← All posts
ai-agentsengineeringbuild-in-publicraspberry-pi

I Hate Making Playlists, So I outsource mine

8 min read

I like music. I don't like making playlists.

Finding songs one by one, adding them, ending up with the same twenty tracks I already know. I spend more time managing music than discovering any. So a while back I built Auralis, a small app that makes the playlist for me. It started as a Streamlit app talking to Gemini, and it was flaky in all the ways you'd expect. Answers drifted, the hosting was a mess, and I stopped opening it. The calls kept getting stuck, and building and managing the context was most of the work. It still added the same songs over and over again. No amount of prompt engineering helped!

This month I rebuilt it from scratch. It now runs on a Raspberry Pi at home and uses Claude as the brain. I can ask it for music from a web page on my local Wi-Fi, or from Discord with a few commands.

The page the Pi serves on my home network. Describe a moment, pick how hard it should research, and go. A real run, trimmed to the key moments.

What it does

You describe a moment, like a quiet Sunday evening hiking in the Black Forest, warm and unhurried, about an hour. Auralis looks at what I actually listen to (recent plays, top tracks, my playlists and all my liked songs), at the time of day, the season and the weather outside, and builds the playlist in my Spotify.

It also has a song of the moment, one new song for right now, and it can post one to Discord every morning and on demand.

Three things I cared about:

  • Discoveries, not a rerun. Songs I already know are allowed, but only a few.
  • No back-to-back repeats. Anything from the last two creations is off the table. Older songs can come back.
  • I stay in control. A dislike means "less like this", never "never again". Songs I haven't rated are neutral, and I can change my mind from a tab any time.

How it works

It isn't a server. It starts when I ask for something and exits when it's done, so nothing sits in memory while it's idle. While it's running, the whole service uses around 100 MB.

Claude gets Spotify as a set of tools through an MCP server, and it can search the web. A run has two stages:

  1. Scout. Reads my taste, searches Spotify, and searches the web only when the request needs it. It has no way to write anything.
  2. Curator. Picks the final list. It has no web access, and it holds the only tool that can create a playlist.

I split it this way on purpose. The agent that reads random web pages can't change anything, and the agent that can change things never reads a web page. If a page ever tried to talk my agent into something, there's nothing for it to grab.

Here is that split as a block diagram. Flip between one agent that holds every tool and the Scout and Curator, then slip a poisoned web page into each:

Untrusted web pageanything the agent might read
YouDescribe a moment
ScoutReads and searches
  • Read my taste
  • Search Spotify
  • Search the web, only when needed
  • Write anything
CuratorPicks the final list
  • Create the playlist, through the gate
  • Touch the web
SpotifyThe playlist

Run a request, or slip a poisoned web page in and see what it can reach.

The tool access in the two blocks is how Auralis is really set up, and the checks are the ones the gate runs. The "one agent" version is the design I chose not to build.

The rules live in code, not in the prompt

My first instinct was to write the rules into the prompt and trust them. Old prompt engineering habits die hard. But that's a request, not a guarantee.

Instead the "create playlist" tool is a gate. It checks every submission and sends back a list of what's wrong, so Claude can fix it in the same session:

  • Every track must have come from a real Spotify lookup, so no invented songs.
  • No duplicates, a sensible length and no more than two songs per artist.
  • Not too many songs I already know, and nothing from the last two creations.

If the playlist is accepted, it's created all at once, and if adding the tracks fails it's rolled back. A preview runs exactly the same checks without touching Spotify.

A preview of a playlist called Black Forest Sunday Dusk with 17 tracks, marked as not created yet

"Show me before creating" is on by default. Nothing exists in Spotify until I press the green button.

That preview took about a minute on the Pi. It gave me Black Forest Sunday Dusk: 17 tracks, from Brahms and Nick Drake through João Gilberto and Bill Evans to Bon Iver. Pressing "Create this playlist" builds exactly what I saw, with no second round of research.

The How Auralis looked panel explaining how the request was interpreted

A short note on how it read my request. I like being able to check its thinking.

Research depth, so it doesn't search when it doesn't need to

Searching the web for every request would be slow and wasteful. If I ask for something that fits my own taste, Claude already has enough to go on. So there are three levels. Quick uses my taste only, Auto searches the web when the request needs it, and Deep researches properly. I pick per request, or let Auto decide.

Discord

Same engine, different front door. A slash command in my own server gives me a song or a playlist:

  • /song for one new song
  • /playlist with a prompt and a depth
  • /history for the latest suggestions

Every song comes with Like and Less like this buttons. The bot only answers me, in my server, and it can't read ordinary messages. It runs inside the same process as the web page, so there's still just one thing running on the Pi.

The morning song, and a /song I typed a minute later. Both explain why they were picked.

Everything it makes lands in a history tab.

The History tab listing the songs Auralis made, newest first, with their type and research depth

The history tab lists everything Auralis has made, newest first.

The likes and dislikes have a tab of their own, where I can change my mind.

The Likes and dislikes tab with Leaning toward and Steering away columns, both still empty

The likes and dislikes tab, still empty because I've only just started rating.

What went wrong along the way

  • Claude found a loophole in my rules. I'd excluded a song, and Claude swapped in a different release of the same song. Every release has its own Spotify ID, so my check passed. Now songs are compared by title and artist, not by ID.
  • A dry run confused it. In a preview, the tool replied that the playlist URL was null. Claude read that as a failure and tried again. Adding a plain "this is a dry run, you're done" message fixed it.
  • Weather wasn't being sent. I asked myself whether the weather and time of day were reaching the prompt. Time of day, yes. Weather, no: I'd never connected it in the new web service, so Claude never saw it. It's fixed now, and the first song after the fix was explained with "the grey autumn afternoon" outside.
  • Spotify has tightened its developer rules. Search returns at most 10 results, there are fewer bulk calls, and it needs Premium. The Spotify layer is built around those limits.
  • A Windows line ending nearly broke the bot. I copied my keys to the Pi from Windows, and every value picked up an invisible carriage return. The weather key was rejected with a very misleading error. Once I spotted it, the fix was one sed command.

Where it stands

It runs on the Pi, it answers from the browser and from Discord, and it's one idle process. I'm happy with the playlists. They feel like something a friend who knows my taste would make.

Still on my list: auto-deploys from GitHub to the Pi.

If you'd like to look at the harness in detail, or want help building your own subagents, feel free to reach out below. Happy to help. And if you have suggestions, or want to try Auralis yourself, I'd love to hear them.

Enjoyed this one? Get the next post by email — no spam, unsubscribe anytime.

Subscribe →