I had let AIRunner, my open-source desktop AI application, do too much in one repository. The split is now visible in four public packages: airunner-eval, airunner-tts-vendor, airunner-native and airunner-common.
The names describe the boundaries. The evaluation package can process models without dragging the application into the run. The TTS vendor fork can be tested and published without importing the host application's database. Native tools can stay close to their sidecar binaries. Shared request and response payloads can be versioned once and consumed by the desktop and service layers.
The web engine moved in the same direction. It stopped naming the products it hosts, moved the world and economy layer into uwuchat-worldsystems, and renamed the service package to airunner_engine. The remaining boundary is visible in the code instead of being a convention in a README. That matters when a new application is added because the engine has fewer reasons to know it exists.
The stack is still familiar: Python, PySide6, HTTP and WebSocket services, package publishing and CI checks. The work was less about picking a new framework and more about making the existing contracts explicit. Import boundaries run in CI. The desktop/daemon contract has a version. Shared LLMRequest and LLMResponse payloads live in airunner-common instead of being copied between packages.
The user-facing site is airunner.art. The four-week release overview covers the other projects that moved alongside it.
The payoff is visible when a new change arrives. A model-evaluation change can stay in the evaluator. A native sidecar change can stay with the sidecar. The desktop app can consume a versioned payload instead of reaching into a folder that happened to be next door. Those are small boundaries, but they remove a lot of guesswork from the next release.
Extraction is worth doing when the package has its own reason to change, its own tests and a public consumer that should not install the rest of the application. If those things are not true, a new repository is just another place to forget to update.
The next AIRunner step is to keep the service boundary just as clear as the first four extractions. The goal is a smaller set of packages that can be installed and released independently while the desktop application still feels like one thing.