Why builds take so long and the fix
How much faster
| Build | Today | After steps 1–4 | After the crate split |
|---|---|---|---|
| API deploy to Contabo (CI) | 66–107 min, measured (one-job first run: 86 min) | 15–25 min on our own builder with a warm build folder | 5–10 min |
| Desktop build, no Rust change | 60–120 min, measured tonight (machine overloaded) | 8–12 min (UI + packaging; binaries reused) | same |
| Desktop build with an API change | 90+ min | 20–30 min | 10 min |
| CI tests | ~50 min, measured | 15–20 min (fast test profile) | 5–10 min |
- 10 minutes is reachable for UI and Desktop builds with steps 1–4. API changes reach 10 minutes only after the crate split. With one 363,000-line crate, the compiler cannot go faster than one core on that crate.
- Step 0 measures before we commit. One
cargo build --timingsof the API with today's settings and with a fast profile, on the builder. Then the estimates in this table become real numbers.
How sessions know a build already has their change
- Every build records the main commit it built. "Has my change" means
git merge-base --is-ancestor M <built commit>. Git answers this exactly. - A queued build reads main when it starts, not when it was queued. Everything merged while it waited goes in. Ten sessions waiting means one build, not ten.
- No polling. A session that must see its change live runs one wait that returns once, when a build with its commit is done. A session that does not need to see it live just merges and finishes.
- Tests stay with the session, before merge (fast profile, only the crates it touched). Only release builds go through the queue.
Splitting the API into crates
It is the norm. Large Rust codebases are many crates in one workspace. The Rust compiler is about 70 rustc_* crates, Zed's editor has over 100 crates and Bevy has about 50. Google and Meta go further, with Bazel and Buck targets per small library.
What it does to the platform: nothing a user sees. It is still one allternit-api binary, the same routes, the same database and the same deploy. The shipped binary keeps LTO, so it runs as fast as it does now.
Debt and cost we take on:
| Cost | Why | How we keep it small |
|---|---|---|
| One shared core crate | 331 of the files use AppState, the one object that holds the database, auth and config |
Move AppState, db, auth and errors into api-core first; every area crate depends on it |
| Cutting cycles | Route modules call into each other; separate crates cannot depend on each other in a circle | Shared code moves to core; a few links become traits |
| Open branches conflict | Moving files breaks every open PR that edits them | One area per PR, announced, merged in a quiet window; pure moves, so git follows the renames |
| Wider visibility | pub(crate) items become pub between crates |
Keep each crate's public surface small |
| More Cargo.toml files | One per crate | Versions stay in the workspace file, as now |
Where to cut first: llm_gateway (25,000 lines) and agency_api (23,000 lines) depend only on db, auth and media. They come out first, after core.
Order of work
| Step | Work | Gain | Size |
|---|---|---|---|
| 0 | Measure: cargo build --timings, today's profile against a fast profile |
Real numbers in place of estimates | 1 hour |
| 1 | Build queue with "contains my commit" | No overlapping builds; one build per batch | Small |
| 2 | Release train rule in AGENTS.md | Sessions merge, the queue ships | Small |
| 3 | Desktop reuses binaries whose source did not change | Desktop build in ~10 min | Small |
| 4 | Fast test profile, sccache in CI, Linux builds on our builder with a warm build folder | CI 66–107 → 15–25 min | Medium |
| 5 | api-core, then llm_gateway and agency_api crates, then the rest one area per PR |
API builds in ~10 min | Large, in steps |