Kanora
A self-hosted music platform for people who own their music
The Mac rips your CDs, records your records and keeps the library; every other screen plays it, with no subscription, no account and no Kanora cloud. It is the largest thing I've built on my own, and where I learned how to build with AI agents at this scale.

- surfaces: Mac, iPhone, iPad, Watch, CarPlay, Apple TV, Android
7
surfaces: Mac, iPhone, iPad, Watch, CarPlay, Apple TV, Android
- commits and 393 merged PRs on the Apple apps in a year
2,700+
commits and 393 merged PRs on the Apple apps in a year
- unit and UI tests across the Apple codebase
3,600+
unit and UI tests across the Apple codebase
- from an empty repo to a native Android client in Kotlin
2 weeks
from an empty repo to a native Android client in Kotlin
In private testing through TestFlight on iPhone, Watch and Apple TV, with a notarised build for the Mac. Figures as of October 2026.
The best parts of old iTunes, without renting my own collection back
Kanora started as a music problem. I have a shelf of CDs and a library I spent years tagging, and nothing modern treats that as the main event; streaming apps treat owned files as an afterthought, and the old desktop tools stopped feeling trustworthy a long time ago.
It became a deliberate engineering project too: a long-lived, multi-device app that touches the awkward corners of the stack, from raw disc reads and USB protocols to background transfers to a watch, built by one person without cutting the corners that make software trustworthy.
The interesting parts
SwiftUI throughout, async/await and actors for concurrency, Core Data for the library, and a set of Swift packages for the design system, companion contracts, transport and MiniDisc. These are the decisions I'd want to talk through.
The Mac is the server
There is no Kanora cloud. The Mac app embeds an HTTP and WebSocket server on SwiftNIO, described by an OpenAPI contract with 43 paths and shared with clients as a Swift package. Phones, the TV and the Android app find it over Bonjour and pair with a short code.
One way to change what is playing
Every playback mutation, whether it comes from the UI, the Companion API, the Watch, CarPlay, Siri or the lock screen, goes through a single PlaybackActor command bus. One producer and one audio engine, by construction rather than by convention.
CD ripping with no ffmpeg
A small C shim over IOKit reads the disc's table of contents and raw sectors, the MusicBrainz disc ID is computed in process, and CoreAudio encodes to FLAC, ALAC or AAC. Removing ffmpeg took the last GPL code out of the bundle, and a licence check in the gate inspects every linked slice so it stays out.
One metadata pipeline
CDs, imported files, live recordings and the existing library all pass through the same acquire, identify, review, commit pipeline, with MusicBrainz and the Cover Art Archive behind a single rate limiter. The portable catalog is the authority and Core Data is a cache that can be rebuilt from it.
A Watch that plays on its own
The iPhone selects, transcodes and transfers music to Apple Watch, which keeps its own store and plays without the phone. It also works as a remote, with Now Playing complications in four families.
Vinyl, cassette and MiniDisc
Line-in capture on AVAudioEngine records analogue sources straight into the library, and MiniDiscKit is a native Swift port of the NetMD protocol over IOKit USB, so old hardware becomes a source rather than e-waste.
One library, designed for each device
Mac
The host: ripping, recording, editing and the library itself.


iPhone
A paired remote at home, and a standalone offline player away from it.







The same product, a second platform, two weeks
The Apple apps took a year. The Android client was seeded on 30 September 2026 as a native app in Kotlin, with Jetpack Compose, Media3 and Room, for phone and tablet plus a Wear OS remote. It follows the Apple client's architecture, pairs with the same Mac over the same companion API, and ships to testers through Firebase App Distribution.
It had its own constitution and wiki by the next morning and ran the same spec, plan, review and QA loop from day one, with a gate covering lint, architecture rules, unit and screenshot tests. Two weeks in it stands at 156 commits and about 69,000 lines of Kotlin, and within the first week I put it at roughly three quarters of the way to the iPhone app.



Agents typing, gates holding
Most Kanora commits were co-authored with Claude, with Codex alongside. What kept that from becoming a feature factory was the process: a constitution, written specs and plans, fresh context reviewers, and a verification gate that builds, tests and launches the app and cannot report a green it did not see.
Over the year that produced 39 architecture decisions, 64 plans, 31 specs and more than 180 reports, from review verdicts to investigations, all in the repository next to the code.
Where it's heading
- A new playback engine phase: gapless, crossfade and ReplayGain, then multi-room
- Encrypted pairing between the Mac and its clients
- The Mac app moving to the Mac App Store, now the binary is free of GPL code
- Moving the whole workspace onto Gatehouse, the toolkit extracted from this process