The app that hates your post-2000 songs. π€
A distributed music streaming platform monorepo designed to explore production-grade architecture for Spotify-like experiences. The "post-2000 rejection" rule is an intentional product experiment used to exercise the event pipeline (Nirvana: yes, Justin Bieber: sorry).
If you try to hack it, you'll still run through validation, AI reasoning, and transcoding stages. All Linkin Park songs are allowed though. Somebody put an IF in the code base, damn! π
Quick links to every component's documentation, organized by layer.
| Package | README |
|---|---|
| @pack/kernel | repos/packages/kernel/README.md |
| @pack/event-inventory | repos/packages/event-inventory/README.md |
| @pack/env-orchestration | repos/packages/env-orchestration/README.md |
| @pack/nats-broker-messaging | repos/packages/nats-broker-messaging/README.md |
| @pack/cache | repos/packages/cache/README.md |
| @pack/patterns | repos/packages/patterns/README.md |
| @pack/neon-tokens | repos/packages/neon-tokens/README.md |
| Service | README |
|---|---|
| Authority | repos/domain/identity/authority/README.md |
| Slim Shady | repos/domain/identity/slim-shady/README.md |
| Service | README |
|---|---|
| Petrified | repos/domain/ai/petrified/README.md |
| Fort Minor | repos/domain/ai/fort-minor/README.md |
| Stereo | repos/domain/ai/stereo/README.md |
| Service | README |
|---|---|
| Soundgarden | repos/domain/streaming/soundgarden/README.md |
| Mockingbird | repos/domain/streaming/mockingbird/README.md |
| Hybrid Storage | repos/domain/streaming/hybrid-storage/README.md |
| Service | README |
|---|---|
| Backstage | repos/domain/realtime/backstage/README.md |
| Component | README |
|---|---|
| Pulse (frontend) | repos/apps/pulse/README.md |
| Shinoda (agent) | repos/agents/shinoda/README.md |
This repository combines a Next.js frontend, domain microservices, and internal platform packages to deliver resilient streaming, modular domain design, and a fast developer workflow.
Pulse is a pnpm workspace monorepo coordinated by Turborepo. The workspace topology isn't incidental tooling β it is the architecture. pnpm enforces explicit dependency boundaries between packages and services. Turbo makes shared packages first-class build inputs, so every service compiles against the same DDD vocabulary without version drift.
pulse/
βββ repos/ # ποΈ All workspaces live under this folder
βββ apps/ # π₯οΈ User-facing applications
β βββ pulse/ # Next.js 16 player UI + BFF
β
βββ agents/ # π€ AI orchestration agents
β βββ shinoda/ # Mastra-powered operations agent
β βββ .agents/ # π Agent context, skills, and plans
β βββ context/
β βββ plans/
β βββ skills/
β
βββ packages/ # π¦ Shared infrastructure libraries
β βββ kernel/ # DDD primitives (Entity, UseCase, EventBusβ¦)
β βββ event-inventory/ # NATS event subject enums
β βββ env-orchestration/ # Env-var helpers + docker-compose.yml
β βββ nats-broker-messaging/ # NATS transport layer
β βββ cache/ # Redis cache abstraction
β βββ patterns/ # Circuit breaker + resilience patterns
β βββ neon-tokens/ # OKLCH neon design tokens
β
βββ domain/ # π· Bounded-context microservices
β βββ identity/
β β βββ authority/ # Auth, JWT, sessions, OAuth
β β βββ slim-shady/ # User profiles + preferences
β βββ streaming/
β β βββ soundgarden/ # Upload ingestion edge
β β βββ mockingbird/ # HLS transcoder
β β βββ hybrid-storage/ # HLS segment store + delivery
β βββ ai/
β β βββ petrified/ # Audio fingerprinting + duplicate detection
β β βββ fort-minor/ # AI transcription (Whisper)
β β βββ stereo/ # AI reasoning + approval/rejection
β βββ realtime/
β βββ backstage/ # Pipeline projection + Socket.IO broadcast
β
βββ .agents/ # π Root-level agent context & plans
The naming convention is intentional and layered:
| Layer | Style | Rationale |
|---|---|---|
apps/ |
Product names | User-facing applications named after what they do |
agents/ |
Music artists | AI personalities with character β any music reference, not a fixed theme |
packages/ |
Functional/descriptive | Infrastructure must be self-evident |
domain/<context>/ |
Business terminology | DDD bounded contexts in plain language |
domain/<context>/<service> |
Music references | Each service has a story (see Nomenclature) |
Every microservice name is a music reference. This isn't decoration β it's a signal that the service has a distinct identity and a single well-scoped responsibility.
| Service | Music Reference | Why It Fits |
|---|---|---|
| Soundgarden | Band name | A garden where you deposit audio β the ingestion entry point |
| Mockingbird | Eminem song | A bird that mimics sounds; the transcoder re-sings the original in new formats |
| Slim Shady | Eminem alter ego | The profile service hides behind the identity β a secondary persona |
| Authority | "Points of Authority" (Linkin Park) | Owns access control for the whole platform β it dictates what you're allowed to do |
| Shinod AI | Mike Shinoda (Linkin Park) | Orchestrates everything β just like Shinoda orchestrates LP's production |
| Petrified | Fort Minor song | Freezes audio identity β the fingerprinting module |
| Fort Minor | Mike Shinoda's side project | Extracts the voice β the transcription module |
| Stereo | Fort Minor song | Dual-channel thinking: merges fingerprint + transcription to decide |
| Hybrid Storage | Hybrid Theory (Linkin Park debut album) | "Hybrid" captures the dual responsibility β it both stores HLS segments to MinIO and serves them as a delivery provider for playback |
| Backstage | Venue metaphor | The place where the real show is observed, not performed |
All Linkin Park songs pass the reasoning stage. Somebody put an
IFin the codebase. π
Every microservice follows the same four-layer structure so the codebase stays navigable regardless of which service you're in:
<service>/
βββ domain/ # Entities, Value Objects, Events, Ports (abstract classes)
βββ application/ # Use Cases β extend UseCase from @pack/kernel
βββ infra/ # DB adapters, NATS wiring, MinIO, Redis, config
βββ interface/ # HTTP controllers, NATS consumers, guards, DTOs, pipes
Ports are abstract classes, not TypeScript interfaces. This is a deliberate convention enforced across the entire repo β it allows NestJS DI tokens to be derived from the port class itself, keeping the adapter wiring clean.
Packages are the architectural glue. They're not utilities β they're the shared vocabulary that keeps every service structurally consistent.
| Package | Exports | Used By |
|---|---|---|
@pack/kernel |
UseCase, DomainEntity, AggregateRoot, ValueObject, DomainEvent, EventBus, Id, EventMap |
All microservices |
@pack/event-inventory |
AuthorityEvent, UserEvent, TrackEvent enums β canonical NATS subject names |
All microservices, agent |
@pack/env-orchestration |
requireStringEnv, requireNumberEnv, optionalStringEnv, optionalNumberEnv |
Service bootstrap |
@pack/nats-broker-messaging |
NatsPublisher, NatsConsumer, NatsEventBusAdapter, NestJS integration, error hierarchy |
All event-driven services |
@pack/cache |
CachePort, RedisCacheAdapter, RedisLike port |
Petrified, Fort Minor |
@pack/patterns |
CircuitBreaker, CircuitBreakerState, UniqueEntityId |
Authority, sync boundaries |
@pack/neon-tokens |
28 OKLCH neon color tokens, Tailwind overrides, gradient utilities (.bg-neon, .text-neon, .bg-neon-warm, .bg-neon-cool), .glassy-surface utility |
repos/apps/pulse |
@pack/kernel is the backend source of truth. Downstream services and packages must adapt to kernel; they must not redefine, shadow, or locally fork kernel abstractions.
Do not modify repos/packages/kernel as part of feature work or package-local fixes unless the task is explicitly a kernel change. If a downstream package conflicts with kernel, fix the downstream package instead of patching kernel.
Pulse is deliberately not a purely async platform. Each transport layer has a purpose:
| Transport | Used For |
|---|---|
| HTTP | Frontend β BFF, BFF β services, control-plane access |
| NATS | Async backend workflow orchestration between microservices |
| Socket.IO | Realtime pipeline visibility from Backstage β frontend |
repos/apps/pulse is a Next.js 16 App Router application that acts simultaneously as the player UI and a lightweight Backend for Frontend (BFF). The design reflects the platform's core rule β no post-2000 songs β through a SynthWave/Retrowave visual identity: deep purples, neon magentas, and glowing gradients that feel like a Linkin Park concert lit up in 1999.
The entire routing model is built around Next.js route groups and parallel slots. Route groups establish top-level layout ownership without affecting URL structure; parallel slots let each UI region render and update independently.
app/
βββ (public)/ # π Unauthenticated layout root
β βββ (auth)/
β βββ login/
β βββ signup/
β
βββ (protected)/ # π Auth-guarded layout root
β βββ (player)/ # Player shell β the main UI
β βββ layout.tsx # Owns the top-level grid, injects all slots
β βββ @gallery/ # β³ Parallel slot β track gallery list
β βββ @uploader/ # β³ Parallel slot β upload dropzone OR reasoning UI
β βββ @user-menu/ # β³ Parallel slot β user avatar + dropdown
β βββ @now-playing/ # β³ Parallel slot β active playback bar
β βββ @track-metadata/ # β³ Nested slot β song title + artist
β βββ @playback/ # β³ Nested slot β prev / play-pause / next + scrubber
β βββ @volume-bar/ # β³ Nested slot β volume control
β
βββ api/ # π BFF proxy routes
β βββ authority/ # Login + signup forwarding
β βββ slim-shady/profile/ # Profile read/update proxy
β βββ soundgarden/tracks/ # Upload proxy
β βββ transport/hls/ # HLS segment delivery
β
βββ infra/ # ποΈ Frontend infrastructure (see below)
βββ shadcn/ # Shadcn component registry (relocated)
βββ immer/ # Immer state updater type helpers
βββ next/ # Next.js PageError + layout type helpers
βββ zod/ # Zod schema primitives
Each parallel slot is a fully independent React subtree. The player layout receives gallery, uploader, now-playing, and user-menu as props and places them into the grid β no slot ever knows the others exist. This means @gallery can refetch its track list, @uploader can swap between dropzone and reasoning mode, and the playback bar keeps playing without any of them interfering with each other.
Interface independence. Because slots are isolated subtrees, a loading state, error boundary, or re-render in one slot is completely invisible to all others. In a Spotify-like player this matters: the gallery can show a loading spinner while the playback bar continues playing uninterrupted. The uploader can crash and recover without touching the track metadata display. There is no shared render tree that a single suspended slot can block.
Streaming per slot. Each parallel slot wraps its own Suspense boundary, which means Next.js can stream each region independently from the server. The page shell and the playback bar appear instantly; the gallery β which needs a data fetch β streams in behind them. The user sees a live player immediately, not a blank screen waiting for all data to resolve.
Mobile collapse without state duplication. The @now-playing slot is itself nested: it owns three inner parallel slots (@track-metadata, @playback, @volume-bar), each independently rendered. On mobile, @volume-bar and @track-metadata are hidden via Tailwind breakpoints and their content surfaced inside the user-menu dropdown instead β atoms are read from two different places in the tree simultaneously, without moving or duplicating state. No route change, no re-mount, no prop tunneling.
The infra/ folder inside app/ mirrors the Clean Architecture convention used by backend services. It is the frontend's adapter layer β third-party tools that need to be isolated from product code live here, not at the root.
app/infra/
βββ shadcn/ # Shadcn UI component registry
β βββ components/
β βββ ui/ # Base components (Button, Input, Card, Sliderβ¦)
β βββ ai-elements/ # AI SDK UI components
β βββ reasoning.tsx # Reasoning collapsible (Vercel AI Elements port)
β βββ shimmer.tsx # Streaming shimmer animation
βββ immer/
β βββ state-updater.type.ts # Typed Immer producer helpers
βββ next/
β βββ page-error.types.ts # Next.js error boundary prop types
βββ zod/
βββ schema primitives # Reusable Zod schemas
Why infra/ for Shadcn? The default shadcn init places components at components/ui/. That works until you have a large codebase where components/ becomes a bucket for everything. Moving Shadcn into infra/shadcn/ makes the intent explicit: these are third-party primitives under an adapter boundary. Product components that compose them live in feature-local lib/ui/ folders, not here. The path alias @shadcn points to app/infra/shadcn/ across the whole app.
The ai-elements/ sub-folder hosts a port of the Vercel AI Elements Reasoning component β the same collapsible reasoning block used in Vercel's AI chat interfaces β adapted to consume the Backstage websocket stream instead of an AI SDK message stream.
State is fine-grained by design β Jotai operates like a marionette at the global level, with each atom controlling exactly one wire: volume, playback position in milliseconds, current track metadata, the gallery list, the session, authentication status. Pulling one wire never disturbs the others. At the component level, React's native state API with Immer handles local mutations β no global store is involved unless the state genuinely needs to cross slot boundaries. There are no large reducers, no context blobs.
app/lib/state/
βββ atoms/
β βββ is-authenticated.atom.ts # atom<boolean>
β βββ is-reasoning.atom.ts # atom<boolean> β gates uploader β reasoning swap
β βββ is-paused.atom.ts # atom<boolean>
β βββ current-track.atom.ts # atomWithImmer<CurrentTrack>
β βββ gallery.atom.ts # atomWithImmer<GalleryTrack[]>
β βββ profile.atom.ts # atomWithImmer<Profile>
β βββ session.atom.ts # atomWithImmer<Session>
β βββ progress.atom.ts # atom<Progress> { milliseconds: number }
β βββ volume.atom.ts # atom<number>
β βββ theme.atom.ts # atom<Theme>
βββ domain/ # Frontend domain types
β βββ current-track.domain.ts
β βββ gallery-track.domain.ts
β βββ progress.domain.ts
β βββ volume.enum.ts # Volume.Loud | Moderate | Quiet | Off
β βββ β¦
βββ mocks/ # Dev/seed data for all atoms
βββ global.data.ts # Primitive initial values (isAuthenticated, isPausedβ¦)
A few key decisions visible in the atom layer:
atomWithImmer for complex shapes. currentTrackAtom, galleryAtom, and profileAtom use atomWithImmer from jotai-immer β mutations are written as Immer draft producers so deeply nested updates (e.g. patching a track's album cover) stay readable without spread chains.
Plain atom for scalars. isReasoningAtom, isPausedAtom, volumeAtom, and progressAtom are plain Jotai atoms β no Immer overhead for simple boolean and number state.
isReasoningAtom as the uploader gate. A single boolean atom controls whether @uploader renders the file dropzone or the live reasoning pipeline UI β no prop drilling, no context, no route change. The @uploader slot reads this atom and swaps its view entirely based on it.
Global data as plain values. app/lib/state/global.data.ts exports JavaScript primitives that seed atom initial values. This keeps atoms themselves free of side-effects and makes the initial state legible at a glance.
The top-level lib/ folder holds shared utilities and UI components that belong to the app but are not specific to any single route. It follows a layered structure:
app/lib/
βββ state/ # Atoms, domain types, mocks, global data (see above)
βββ hls/ # HLS player integration
β βββ hls.tsx # HLS provider component
β βββ hls-loader.hook.ts # hls.js attach + lifecycle management
β βββ hls-media-session.hook.ts # Media Session API wiring
β βββ load-media.compute.ts # URL resolution for HLS manifests
βββ template/ # Formatting, CSS class metadata, i18n
β βββ formatters/
β β βββ cn.fmt.ts # clsx + tailwind-merge utility
β β βββ msToTime.fmt.ts # milliseconds β MM:SS display
β β βββ currency.fmt.ts
β β βββ to-initials.fmt.ts
β βββ classes/ # CSS layers (imported in globals.css)
β β βββ base.css root.css app.css
β β βββ theme.css # Shadcn CSS variable overrides
β β βββ dark.css # Dark mode token overrides
β β βββ neon.css # SynthWave gradient utilities (see below)
β βββ i18n/
βββ ui/ # Shared server components (Header, Logo)
βββ report/ # Logging utilities
Colocation pattern. Feature-specific code lives inside its route folder β not scattered across a global lib/. A good example is the login page's form/ folder: form.tsx, form.handlers.ts, form.mappers.ts, and form.types.ts all sit side by side in one directory. Everything that belongs to that form is in one place; you never have to hunt across the tree. The global lib/ only holds what is genuinely cross-cutting β if you delete a route, you delete its folder and nothing else breaks.
The visual identity combines three layers:
1. @pack/neon-tokens (workspace package) defines the raw neon color scale β 28 tokens from --ps-neon-01 (deep violet) through --ps-neon-28 (warm amber), covering the full SynthWave spectrum. It also generates the gradient utilities:
/* packages/neon β consumed by apps/pulse via @pack/neon */
.bg-neon { background: linear-gradient(135deg, var(--ps-neon-01) β¦ var(--ps-neon-24)) }
.text-neon { background-clip: text; color: transparent; background: var(--gradient-neon) }
.bg-neon-warm { background-image: var(--gradient-neon-warm) }
.bg-neon-cool { background-image: var(--gradient-neon-cool) }2. lib/template/classes/theme.css overrides Shadcn's CSS variables (--background, --foreground, --primary, --border, etc.) to map onto the neon palette, so every Shadcn primitive β Button, Card, Slider, Input β automatically picks up the retrowave color scheme without per-component overrides.
3. lib/template/classes/neon.css (imported via lib/template) layers additional app-specific Tailwind utilities on top. The .glassy-surface class that gives the gallery and uploader cards their frosted-glass appearance lives here.
The result: the entire UI, including Shadcn primitives, renders in the neon theme without a single inline style or arbitrary Tailwind value.
When a user uploads a track, the @uploader slot transitions from dropzone to a live pipeline feed β narrated, human-readable events appear in real time, one by one, as the track moves through fingerprinting, transcription, and AI reasoning. The user sees exactly what the platform is doing with their file, instantly and without polling.
Backstage is the broadcaster. Every microservice publishes events to NATS using a track.* subject pattern. Backstage subscribes to the entire track.> wildcard, projects each event into a MongoDB pipeline read model, and immediately broadcasts it over a Socket.IO /pipeline namespace. This makes Backstage the single source of realtime truth for the frontend β services don't need to know the UI exists.
NATS (track.> wildcard)
β All track.* events from every microservice
βΌ
Backstage
β Projects to MongoDB pipeline read model
β Broadcasts pipeline.event over Socket.IO /pipeline namespace
βΌ
useReasoningSocket() hook (in @uploader slot)
β WebSocket connection to Backstage /pipeline namespace
β Accumulates events into an ordered list as they arrive
βΌ
ReasoningPipeline component
β Passes accumulated content to <Reasoning isStreaming={β¦}>
β Wraps each message in <Shimmer> while streaming
βΌ
User sees live narration β fingerprinting β transcription β GPT-4o verdict
The Reasoning component β ported from Vercel AI Elements and adapted in app/infra/shadcn/components/ai-elements/reasoning.tsx β is a collapsible panel with a <Shimmer> animated header that pulses while events are arriving. It uses streamdown for markdown-aware streaming text rendering and auto-closes after an idle timeout once the pipeline goes quiet.
Pipeline event messages are deliberately written as narration, not log lines:
Upload received. Let's see what you've got.
Checking file integrity... looks good.
Audio fingerprint computed.
Lyrics decoded. Mike Shinoda would approve.
AI cognition engine analyzing the track.
Track approved for the pipeline.
Audio transcoding in progress.
Track fully processed. Ready for playback.
If a track is rejected (post-2000 song detected, duplicate found, or metadata hacked), the reasoning feed surfaces the rejection reason directly in the UI β including a suitably sarcastic message when the AI catches someone trying to sneak in a Justin Bieber track.
Tailwind breakpoints combined with Jotai's fine-grained atoms make the mobile experience a layout concern, not a state concern. Nothing is re-fetched or re-initialized when the viewport changes.
On desktop the player grid renders all four slots side by side. On mobile:
@uploaderis hidden (mobile-hiddenutility on its Card wrapper) β upload is a deliberate desktop-first action@now-playing/@volume-barcollapses out of the playback bar@now-playing/@track-metadatamoves into the user-menu dropdown
The volume atom and current-track atom stay exactly where they are β atoms don't care about viewport. The user-menu slot reads volumeAtom and currentTrackAtom directly and renders them in the dropdown only when the volume bar slot is hidden. No duplication, no prop tunneling.
Note: The HLS delivery and playback pipeline is currently under development and not yet functional end-to-end. The BFF proxy routes and hls.js integration are in place, but the full playback experience is not ready.
BFF routes in app/api/ are thin by design β they exist to own the service URL configuration and x-request-id header, not to add business logic:
api/authority/loginandapi/authority/signupβ validate bodies with Zod, forward to Authority, normalize errorsapi/slim-shady/profileβ read/update proxy to Slim Shadyapi/soundgarden/tracksβ multipart upload proxy; triggers the slot swap to the reasoning view on successapi/transport/hlsβ HLS manifest and segment delivery; resolves object storage URLs and proxies segment bytes to the browser
HLS + Media Session API. The app/lib/hls/ layer wraps hls.js with two hooks:
hls-loader.hook.ts handles the hls.js instance lifecycle β attaching to the <audio> element, loading the manifest URL, and tearing down cleanly on unmount.
hls-media-session.hook.ts wires the Media Session API so that OS-level media controls (lock screen widget, headphone buttons, AirPods double-tap, notification shade on Android) stay in sync with the player state. Track title, artist, album art, and transport actions (play, pause, previous, next) are all registered through navigator.mediaSession and updated whenever currentTrackAtom changes.
The full track lifecycle β from the moment a user drops a file to the moment it plays β is an event graph across eight services.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER UPLOADS TRACK β
β apps/pulse (BFF) β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β HTTP POST /api/soundgarden/tracks
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SOUNDGARDEN β
β Upload ingestion edge β
β β
β track.upload.received β track.upload.validated β
β track.upload.stored β track.uploaded βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββ
β track.uploaded
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PETRIFIED β
β Acoustic fingerprinting service β
β β
β Chromaprint fingerprint β audio hash β duplicate check β
β track.petrified.generated / track.duplicate.detected β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β β
β track.petrified.generated β track.petrified.generated
βΌ βΌ
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββ
β FORT MINOR β β STEREO β
β Transcription service β β Waits for both signals β
β β β β
β OpenAI Whisper (audio) β β ββββ fingerprint state β
β + gpt-audio reasoning β β ββββ transcription state β
β β β β β
β track.fort-minor.* ββββΆβ GPT-4o reasoning β
ββββββββββββββββββββββββββββ β β
β track.approved / track.rejected β
ββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββββββββ
β track.approved
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MOCKINGBIRD β
β Transcoding worker β
β β
β Downloads source from MinIO β
β 128 kbps MP3 + 320 kbps MP3 β HLS segmentation β
β β
β track.transcoding.started β track.transcoding.completed β
β track.hls.generated βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββ
β track.hls.generated
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HYBRID STORAGE β
β HLS persistence + delivery sink β
β β
β Persists .m3u8 playlists + .ts segments to MinIO β
β track.hls.stored ββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββ
β track.hls.stored
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRACK READY FOR PLAYBACK β
β β
β apps/pulse requests HLS manifest via BFF β
β hls.js fetches segments β Media Session API wired β
β OS media controls: artist / title / artwork / transport β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β β β β β β β β β β β β β β β β β β β β β
BACKSTAGE
β Observes ALL track.* events β
Projects to MongoDB pipeline read-model
β Broadcasts pipeline.event over Socket.IO β
β β β β β β β β β β β β β β β β β β β β β β β β β
All NATS subjects follow lowercase dot-delimited patterns:
| Pattern | Example |
|---|---|
<domain>.<entity>.<state> |
authority.user.signed_up |
<entity>.<subentity>.<state> |
user.profile.updated |
track.<stage>.<state> |
track.upload.received |
The NATS subject is the event name string β emit('track.uploaded', payload) publishes to NATS subject track.uploaded. No mapping layer.
| Subject | Producer | Consumers |
|---|---|---|
authority.user.signed_up |
Authority | Slim Shady |
user.profile.created |
Slim Shady | Authority |
authority.user.logged_in |
Authority | Backstage |
authority.token.refreshed |
Authority | Backstage |
authority.user.logged_out |
Authority | Backstage |
track.upload.received |
Soundgarden | Backstage |
track.upload.validated |
Soundgarden | Backstage |
track.upload.stored |
Soundgarden | Backstage |
track.uploaded |
Soundgarden | Petrified, Backstage |
track.upload.failed |
Soundgarden | Backstage |
track.petrified.generated |
Petrified (7201) |
Fort Minor, Stereo, Backstage |
track.petrified.song.unknown |
Petrified (7201) |
Backstage |
track.duplicate.detected |
Petrified (7201) |
Backstage |
track.petrified.failed |
Petrified (7201) |
Backstage |
track.fort-minor.started |
Fort Minor (7202) |
Backstage |
track.fort-minor.completed |
Fort Minor (7202) |
Stereo, Backstage |
track.fort-minor.failed |
Fort Minor (7202) |
Backstage |
track.stereo.started |
Stereo (7203) |
Backstage |
track.approved |
Stereo (7203) |
Mockingbird, Backstage |
track.rejected |
Stereo (7203) |
Backstage |
track.stereo.failed |
Stereo (7203) |
Backstage |
track.transcoding.started |
Mockingbird | Backstage |
track.transcoding.completed |
Mockingbird | Backstage |
track.transcoding.failed |
Mockingbird | Backstage |
track.hls.generated |
Mockingbird | Hybrid Storage, Backstage |
track.hls.stored |
Hybrid Storage | Backstage |
The AI cognition layer is split into three independent microservices, each with its own infrastructure, persistence, and deployment boundary:
domain/ai/
βββ petrified/ β acoustic fingerprinting (Chromaprint) β Redis + MinIO
βββ fort-minor/ β speech-to-text transcription (Whisper) β Redis + MinIO
βββ stereo/ β AI reasoning, approval/rejection (GPT-4o) β MongoDB
Petrified (port 7201) runs on every upload. It generates a Chromaprint fingerprint and an audio hash, checks for duplicates via its own Redis (petrified-redis), and emits the fingerprint payload that unblocks both Fort Minor and Stereo.
Fort Minor (port 7202) transcribes the audio. Using OpenAI's Whisper model (via the AI SDK), it extracts lyrics, detects language, and produces structured transcription segments. It has its own Redis (fort-minor-redis) for idempotency and MinIO (fort-minor-minio) for audio access.
Stereo (port 7203) is the decision engine. It waits until both fingerprint state and transcription are present, then runs GPT-4o reasoning over the combined signals to emit track.approved or track.rejected. It persists state in its own MongoDB (stereo-mongo). The pre-2000 product rule lives here β not as a hard-coded check but as a reasoning prompt, so the rule can be tuned, loosened, or entirely replaced without touching the pipeline plumbing.
repos/agents/shinoda is a Mastra-based AI agent that acts as the platform's operational intelligence layer for developers. It is not a runtime microservice β it is a developer tool that connects to the same service APIs and event projections used by the platform.
repos/agents/
βββ shinoda/
βββ src/
βββ shinoda/
β βββ shinoda.agent.ts # Agent identity + system prompt
β βββ tools/
β β βββ analyse-pipeline # Inspect pipeline state per trackId
β β βββ inspect-events # Query event history and detect gaps
β β βββ check-services # Health-check all 9 platform services
β βββ signals/
β β βββ signal-bus.ts # Typed EventEmitter singleton
β β βββ anomaly-rules.ts # Stuck track, gap, out-of-order detection
β β βββ monitor.ts # Socket.IO + polling continuous monitor
β βββ workflows/
β β βββ debug-pipeline # Multi-step diagnostic workflow
β β βββ health-pipeline # Parallel service health checks
β βββ infra/mcp/
β βββ mcp-client.ts # Generic MCP client factory
β βββ observability-sink # Signal bus β MCP server bridge
βββ mastra/index.ts # Mastra registration + signal subscribers + MCP wiring
βββ index.ts
Shinoda runs a local dev server via mastra dev on http://localhost:4111 and is grounded in:
- context documents (
repos/agents/.agents/context/) β the full AI pipeline context - plans (
repos/agents/.agents/plans/) β implementation roadmaps per milestone - skills (
repos/agents/.agents/skills/mastra/) β Mastra SDK references and migration guides
The agent is designed to answer questions like:
- "Why was this track rejected?"
- "What events were emitted for trackId X?"
- "Is the pipeline stalled after fingerprinting?"
Read-only by default, not by design. Shinoda currently observes and diagnoses β it never modifies platform state. This is a deliberate starting point, not a permanent constraint. The signal layer (signal-bus.ts, anomaly-rules.ts) emits typed events like TRACK_STUCK and SERVICE_UNHEALTHY. The health pipeline workflow checks all 9 services in parallel and aggregates results. When MCP_SERVER_URL is configured, the ObservabilitySink automatically forwards all signals to an external MCP server for integration with OpenTelemetry collectors, bug trackers, alerting platforms, or any observability tooling.
Shinoda is developer/operator-facing, not end-user-facing. It uses openai/gpt-4o-mini by default (cost-efficient for operational tooling) with a clear upgrade path to GPT-4o for deeper reasoning tasks.
| Service | Port | Persistence | Role | Health |
|---|---|---|---|---|
authority |
7000 |
MongoDB (authority-mongo) |
Auth, JWT, OAuth, sessions | /health |
slim-shady |
7400 |
MongoDB (slim-shady-mongo) |
User profiles | /health |
soundgarden |
7100 |
MinIO (soundgarden-minio) |
Upload ingestion | /health |
petrified |
7201 |
Redis (petrified-redis), MinIO (petrified-minio) |
Audio fingerprinting | /health |
fort-minor |
7202 |
Redis (fort-minor-redis), MinIO (fort-minor-minio) |
AI transcription | /health |
stereo |
7203 |
MongoDB (stereo-mongo) |
AI reasoning | /health |
mockingbird |
7200 |
MinIO (mockingbird-minio) |
HLS transcoding | /health |
hybrid-storage |
7300 |
MinIO (hybrid-storage-minio) |
HLS persistence | /health |
backstage |
4001 |
MongoDB (backstage-mongo) |
Pipeline observation + Socket.IO | /health |
pulse |
3000 |
Browser / Jotai | Frontend + BFF | β |
shinoda |
4111 |
β | AI operations agent | β |
Every microservice owns its infrastructure β no shared databases or caches. The only shared component is NATS.
| Dependency | Instances | Role |
|---|---|---|
| MongoDB | authority-mongo, slim-shady-mongo, stereo-mongo, backstage-mongo |
Per-service persistence |
| Redis | petrified-redis, fort-minor-redis |
Idempotency, audio hashes |
| NATS (JetStream) | nats |
Shared async event plane |
| MinIO | soundgarden-minio, petrified-minio, fort-minor-minio, mockingbird-minio, hybrid-storage-minio |
Per-service object storage |
pnpm infraThis single command spins up the entire platform β all per-micro infrastructure (MongoDB Γ4, Redis Γ2, MinIO Γ5, NATS) and all 11 application services β using the docker-compose.yml under repos/packages/env-orchestration/. No manual service wiring required. MinIO buckets are bootstrapped automatically by init containers on first run.
pnpm dx:env:templateScans the monorepo for every .env.template file and generates .env files from them. Run this once after cloning to get a working local environment. Re-running it is safe β it restores any accidentally deleted or corrupted .env files without touching values you've already customized.
| Script | Description |
|---|---|
pnpm infra |
Raise the entire docker environment (infra + all services) |
pnpm pulse |
Start the Pulse frontend in dev mode (requires infra already running) |
pnpm dx:env:template |
Generate .env files from all .env.template files in the monorepo |
pnpm build |
Build all packages and services through the Turbo build graph |
pnpm lint |
Biome lint across the entire monorepo |
pnpm format |
Biome format with VCS-aware ignore rules |
pnpm typecheck |
TypeScript type-check across all workspaces |
Turbo treats shared packages as first-class build inputs. @pack/kernel must be built before any service that depends on it β Turbo infers and parallelizes this graph automatically. You never need to manually order package builds.
There are no unit, integration, or end-to-end tests in this repository. This is intentional.
The backend was built to validate a wired event-driven architecture from the ground up. Every microservice exposes a standard GET /health endpoint returning { status: 'ok' }, and Docker Compose health checks enforce boot order. The Shinoda agent provides automated health monitoring β its health pipeline workflow checks all 9 services in parallel and emits SERVICE_UNHEALTHY signals for any failures. These signals can optionally be forwarded to external observability platforms via MCP.
Adding unit or integration tests at this stage of a personal MVP would mean a substantially larger codebase β more code to write, more code to maintain, and more code to refactor as the architecture evolves. In a microservices system where domain logic crosses service boundaries via NATS events, integration tests also require the full infrastructure to be running, which duplicates what pnpm infra already provides.
The trade-off is deliberate: move fast, validate the architecture, then layer in tests as the codebase stabilises.
Make sure you have the following installed:
- Node.js
>= 22.13.0 - pnpm
>= 10.30.3(npm install -g pnpm) - Docker + Docker Compose
git clone https://github.com/your-org/pulse.git
cd pulsepnpm installpnpm dx:env:templateThis walks every workspace and generates .env files from .env.template. Fill in your secrets (OpenAI API key, MinIO credentials, JWT secrets) before starting services.
pnpm infraThat's it. This command:
- πΆ Starts MongoDB Γ4 (authority, slim-shady, stereo, backstage)
- π΄ Starts Redis Γ2 (petrified, fort-minor)
- π’ Starts NATS with JetStream enabled
- πͺ£ Starts MinIO Γ5 (soundgarden, petrified, fort-minor, mockingbird, hybrid-storage) and pre-creates buckets
- π Starts all 11 application services with their health checks
Services are started in dependency order. Health checks ensure no service starts before its dependencies are ready.
To run the Shinoda developer agent locally:
cd repos/agents/shinoda
pnpm dev # starts mastra dev server at http://localhost:4111Open http://localhost:4111 to interact with Shinoda β ask it about pipeline state, event history, or why a track was rejected.
With the full environment running via pnpm infra, the only application you need to start in dev is the frontend. From the repo root:
pnpm pulseThis starts the Pulse Next.js app in dev mode with hot reloading. All backend services are already running inside Docker via pnpm infra.
| Service | URL | Health |
|---|---|---|
| Pulse (frontend) | http://localhost:3000 | β |
| Authority | http://localhost:7000 | /health |
| Slim Shady | http://localhost:7400 | /health |
| Soundgarden | http://localhost:7100 | /health |
| Petrified | http://localhost:7201 | /health |
| Fort Minor | http://localhost:7202 | /health |
| Stereo | http://localhost:7203 | /health |
| Mockingbird | http://localhost:7200 | /health |
| Hybrid Storage | http://localhost:7300 | /health |
| Backstage (HTTP + WS) | http://localhost:4001 | /health |
| Shinoda Agent (Mastra) | http://localhost:4111 | β |
| NATS Monitor | http://localhost:8222 | β |



