Self-hosted LiteLLM gateway on the app host (port 4000).
Settings
Models, data, memory and appearance.
Where you left off The shell has not recorded a surface yet — this is the first page it has seen in this browser.
Language model rails
The three rails the chat surface can talk to. Test asks the rail for its model list right now; Use makes it the rail the next chat turn goes out on. Nothing is probed on page load, so every rail starts out not checked — a state this page states rather than guesses at.
OpenCode Go router (DeepSeek, GLM, and others). No key is stored for this rail, so a test will most likely come back as a rejected key.
9Router passthrough with the rotating Gemini pool. No key is stored for this rail, so a test will most likely come back as a rejected key.
Vertex AI via the mounted service account (no API key needed).
DeepSeek platform API (deepseek-chat / deepseek-reasoner). No key is stored for this rail, so a test will most likely come back as a rejected key.
Provider and model
Any provider in the registry, not just the three rails above. Changing the provider applies that provider's default model; changing the model applies the model in the box.
In use: opencode_go · deepseek-v4.1-flash
Ingest & sync
Every category's last successful run, the interval it runs on and why a category is marked stale. Sync now runs every due category — and it is safe to press twice: the second press starts nothing and the table says so instead of starting a second run.
2 of 7 categories are behind their own interval right now.
Ingest health
Why: Spotify rate-limited the call (HTTP 429) — the next interval retries.
The last runs of every job, with rows in and out and the verdict each one earned, are on Job history.
Spotify connection
Read from the token broker when this page rendered — a measurement of the token that exists right now, not a cached flag. The shell's “Connected — live data” mark above is about the app's link to Spotify data; this is about the token it rests on.
Reading the connection…
Asking GET /api/spotify/connection for who this token
belongs to.
- Product
- —
- Token expires
- —
- Verified
- —
- Refresh
- —
Scopes on this token
—
| Scope | On the token |
|---|
Re-verify asks Spotify /v1/me whether the stored token still
works. Re-consent reopens Spotify's consent screen (it is the only way to
ask for a scope a token does not carry). Disconnect deletes the stored
token for the active account — the app keeps working, the Spotify-backed
features stop until you sign in again.
- Read from
- broker
- Token expires
- unknown
- Scopes granted
- not measured — the token carries no scope string
- Measured at
- 2026-11-10T23:56:03+00:00
The token lives in the app's token broker — this app has no Spotify sign-in page of its own, so reconnection is a broker-side re-consent. The live probes (owner profile, playback and scope coverage) are on Status.
The reader's own words: the broker has no readable token document
11 scopes of the 11 this app asks for are not on the token, so the features built on them are hidden rather than offered and failing:
user-read-private— Owner profile (display name, country, product)user-read-email— Owner profile (display name, country, product)user-read-playback-state— What is playing now (playback state, current track)user-read-currently-playing— What is playing now (playback state, current track)user-modify-playback-state— Transport control (play, pause, skip, queue)user-library-modify— Save albums and tracks to the libraryuser-follow-modify— Follow and unfollow artistsplaylist-modify-public— Playlist write-back (create, edit, reorder, delete)playlist-modify-private— Playlist write-back (create, edit, reorder, delete)playlist-read-private— Private and collaborative playlist readplaylist-read-collaborative— Private and collaborative playlist read
Scopes on the token
Read from GET /api/spotify/me (who the token belongs to)
and GET /api/spotify/capabilities (the measured scope
report) when this page loads. A scope the token lacks is listed with
the feature it belongs to, because “missing scope” and “feature hidden”
are the same fact.
Not read yet — the table is built from the API's answer, never from this template.
This is the one route that starts consent again: it goes to the
Appwrite broker's consent page, and redirects to the app's own PKCE
login (/api/spotify/login) when no broker is configured.
Memory & retention
These windows are read-only here. They are deployment
constants — spotify_ai.services.data_lifecycle.RETENTION — that the sweeps and
the export bucket's lifecycle rule read, so this page shows them and
does not pretend to change them. There is no switch for them on purpose.
- Chat memories
- 365 days
- Older entries are retired by the sweep; until then they list and can be retired by hand on the memory page.
- Job history
- 90 days
- Enough to read a quarter of ingest and learn passes.
- Stored exports
- 30 days
- The same number the export bucket's lifecycle rule uses.
- Erasure audit
- kept forever
- Kept forever: sweeping the proof of a deletion would defeat the point of writing it.
What is stored right now: 265 memories, newest written 2026-11-10 23:35 UTC.
To review, edit or retire an individual memory, use Memory — retiring one stops it being recalled and keeps it readable, which is a reversible act this page cannot perform for you.
Appearance
The same B-7 control the sidebar carries, stated once more where the
rest of the settings are. The choice is a plain link — ?theme=
is what the server resolves before the first paint — and
theme.js stores it for later pages.
Currently auto: no theme is stored, so the page follows this device's colour scheme. Picking Dark or Light stores an explicit choice; picking Auto again clears it.
Debug & verbose
The live debug console records what the shell actually sent and received — fetch and EventSource traffic, the AI intent/SQL events of a chat turn, Spotify player syncs, Hindsight recalls and every failure — in a panel that floats over every page. It is off by default and records nothing at all while off; there is no background sampling to switch away from.
Live debug console
The same switch as the header's bug button: both faces read one value
(debugEnabled) from the shell's Store, so they cannot
disagree.
The tab the console opens on. Switching a tab never discards rows — the panel keeps the newest 400 and drops the oldest. Pause in the panel buffers new entries instead of dropping them, and Clear starts the log over.
The console is mounted outside the page shell, so it survives page
changes: open it here, navigate to Chat or Library, and it is still open
with its rows intact. Its position and filter are stored as
debugPosition and debugFilter. Press
d anywhere to toggle it and Esc to collapse it.
Keyboard shortcuts
The player and shell keys that work without a mouse. They are ignored while you are typing in a text field, so a message that contains s is a message and not a shuffle.
| Key | Action |
|---|---|
| Space | Play or pause the mini player |
| ← → | Seek 10 seconds back or forward |
| s | Toggle shuffle |
| r | Cycle repeat off → context → track |
| d | Turn the debug console on or off |
| Esc | Collapse the debug console (again to close it) |
Player keys act on the mini player only when Spotify reports a device it can control; when it does not, the player says so rather than pretending the key did something.