
On 12 October my Mac hit the wall. The system load swung between 100 and 260, and tools started failing with ENFILE: the system refused to open a single file more. Several agent sessions were working in parallel, each with its own native builds, Docker containers or mobile simulator. All of it on a MacBook Pro M1 Max with 64 GB of memory.
That Mac is the first server of Orazaka, the AI engine I'm building. The idea behind Orazaka is simple: run AI on a machine you control. For me, that machine is my laptop first. Here's what it taught me.
An AI platform is a lot of cooks in a small kitchen
Even without AI, a platform needs several things at once: a database, a message broker, the Java services themselves. Add a language-model server, an image engine, a worker for video. They all share the same memory. When two big dishes cook at once, the oven gives out.
My answer on 12 October was simple, almost dumb: a lock. Before a heavy task (a native build, a Docker stack, a generation), a session creates a .heavy.lock folder. If the folder already exists, it waits a minute and tries again. Like the single bathroom key in a café: one person at a time, the others wait. Everyone slows down a little, but nothing collapses any more.
The Java services take their share too. Orazaka has up to eleven Spring Boot services. An open proposal (orazaka#132) estimates 4 to 6 GB at rest for the lot, and suggests a single-JVM development profile. Those are estimates, not measurements, and it hasn't shipped.
The video model that doesn't fit
I tried generating video locally with Wan 2.2 TI2V 5B, vertical, 480×832 and 25 frames. The model ran out of memory while decoding: it asked for 26 GiB while 62 GiB were already taken on a 64 GB machine. At 704×1280, same story. When it did move, each step took about 32 seconds, according to my working notes.
The lesson is plain: the Mac's unified memory is generous for images, not for video. More on that below.
Spring Boot 4, Spring AI 2, and models that become data
In June I moved the platform to Spring Boot 4.0 and Spring AI 2.0 (ADR-030). That brings Spring Framework 7 and Jackson 3, which moves com.fasterxml.jackson to tools.jackson. The version bump interested me less than what it made possible.
Before, the choice of model lived in configuration: a spring.ai.* block in application.yml, and starters that wire clients for you. Switching models meant redeploying. I removed all of it. Today an ai_providers table holds each provider's address and key, and an orazaka_models table links a model name to its provider. A DynamicModelRouter reads those rows on every call and caches the client.
Think of a phone book. Before, every number was carved into the wall. Now it's written in the book: change one line and the next call goes to the right place, no renovation needed. An admin edits a row, and the platform follows.
One detail makes this work: Ollama and LocalAI speak the same protocol as OpenAI. A single OpenAI-compatible client, pointed at an address read from the database, covers every local provider.
The ClassCastException that killed the chat
Of course it broke. Everything goes through the OpenAI-compatible client, which expects OpenAiChatOptions. But a step earlier in the pipeline tagged local providers with OllamaChatOptions. The result: a ClassCastException, and no chat at all.
The fix, in RoutedChatModel, converts the options before the call:
static Prompt toOpenAiPrompt(Prompt prompt) {
ChatOptions options = prompt.getOptions();
if (options == null || options instanceof OpenAiChatOptions) {
return prompt;
}
OpenAiChatOptions.Builder builder =
OpenAiChatOptions.builder()
.model(options.getModel())
.temperature(options.getTemperature())
.maxTokens(options.getMaxTokens());
// ...tool callbacks, when present
return new Prompt(prompt.getInstructions(), builder.build());
}
There was a second, quieter trap. A step that asked for qwen2.5:7b sometimes went to hosted inference instead, because the model name was ignored except for two providers. Since orazaka-ai-engine#13, routing follows the catalogue's link between a model and its provider. And a model whose provider doesn't answer now fails with an error naming both, instead of quietly going somewhere else. For sovereign AI, that kind of silence is the worst bug there is.
The disk that forgets and the tunnel that drops
Two more problems came from mixing the Mac and the cloud. The dev platform runs on DigitalOcean, and the Mac joins it through a tunnel, via a relay.
First: generated media were written to the container's disk, which every deploy wipes. The database rows survived; the files didn't. We now go through durable storage, with a Bunny adapter (orazaka-ai-engine#21).
Second: every deploy restarts the relay and drops the tunnel. A clip halfway through rendering was lost, then started again from zero. The worker now reconnects and never processes the same job twice (orazaka-worker-media#24).
What the Mac does well
For everything else, the Mac is a great place to start. It runs chat with Ollama, voice with Piper, music with ACE-Step and images with stable-diffusion.cpp. A FLUX.2 [klein] 4B image at 1024×1024 takes 62 seconds at the median, command to file. No bill, no cloud account, no driver to install. And every new developer already has a laptop.
Three pictures from that machine. Under each, "How this was made" gives the prompt, the seed, the time and the Orazaka run that produced it.

How this was made
- Model
- FLUX.2 [klein] 4B (Q8_0 GGUF) · Apache-2.0
- Engine
- stable-diffusion.cpp on Metal, 4 steps, guidance 1.0
- Machine
- MacBook Pro, Apple M1 Max, 64 GB (Metal)
- Through
- Orazaka Studio run on dev.orazaka.com
- Studio
art-miniature- Prompt
A tiny winter park in Montreal: figurines skating on a frozen pond, a small plain wooden cabin with a glowing window and no signboard, snow-covered spruce trees and warm string lights at dusk. Tilt-shift miniature diorama photograph: handcrafted figurines and props at 1:24 scale, macro lens, extremely shallow depth of field with a thin plane of focus, soft window light, ultra-detailed textures of felt, embroidered thread, painted resin and carved wood. Warm ochre, saffron and indigo palette, playful and tender. No text, no lettering, no logos.
- Seed
1643- Size
- 1360×768
- Time
- 75 s, as recorded by the run
- Cost
- no cash cost (own hardware; electricity not metered)
- Orazaka run
- not recorded

How this was made
- Model
- FLUX.2 [klein] 4B (Q8_0 GGUF) · Apache-2.0
- Engine
- stable-diffusion.cpp on Metal, 4 steps, guidance 1.0
- Machine
- MacBook Pro, Apple M1 Max, 64 GB (Metal)
- Through
- Orazaka Studio run on dev.orazaka.com
- Studio
art-miniature- Prompt
A tiny souk alley: figurine spice merchants behind pyramids of saffron, paprika and cumin, hanging pierced-brass lanterns, woven baskets, a cat asleep on a blue doorstep. Tilt-shift miniature diorama photograph: handcrafted figurines and props at 1:24 scale, macro lens, extremely shallow depth of field with a thin plane of focus, soft window light, ultra-detailed textures of felt, embroidered thread, painted resin and carved wood. Warm ochre, saffron and indigo palette, playful and tender. No text, no lettering, no logos.
- Seed
1212- Size
- 1024×1280
- Time
- 100 s, as recorded by the run
- Cost
- no cash cost (own hardware; electricity not metered)
- Orazaka run
- not recorded

How this was made
- Model
- FLUX.2 [klein] 4B (Q8_0 GGUF) · Apache-2.0
- Engine
- stable-diffusion.cpp on Metal, 4 steps, guidance 1.0
- Machine
- MacBook Pro, Apple M1 Max, 64 GB (Metal)
- Through
- Orazaka Studio run on dev.orazaka.com
- Studio
art-montreal-night- Prompt
A narrow back alley of the Plateau between plain brick walls with no signs, exterior spiral staircases, wooden balconies with potted plants and parked bicycles, a lone cyclist far away. Night street photograph in Montreal after rain: wet cobblestones and asphalt mirroring warm window light and a neon glow of magenta and teal, light mist, 35mm lens at f/1.8, cinematic colour grading, deep blacks, rich textures of old brick, wrought-iron spiral staircases and wet pavement. No readable signs, no lettering, no logos.
- Seed
12- Size
- 1360×768
- Time
- 109 s, as recorded by the run
- Cost
- no cash cost (own hardware; electricity not metered)
- Orazaka run
- not recorded
They're bigger than 1024×1024, hence 75 to 109 seconds each.
Where the laptop gives up
Video goes elsewhere. Hosted, a five-second Wan 2.2 A14B clip at 832×480 takes 131 seconds at the median. On a rented H100 we get to 720p. So video goes to a GPU when I choose to send it, and the rest of the work stays on the laptop. The numbers, the bills and the mistakes behind that choice are in the post about costs.
What you can take away
If you're starting an AI platform, start on your laptop: it's the cheapest machine you already own. Put a lock on heavy tasks before the machine does it for you. Keep the choice of models in data rather than in code. And never let a call go to another model without saying so.
What I haven't measured: the Mac's memory and swap over a working day, the real memory of the JVMs at rest, and local chat speed. I use them every day, but I don't have a recorded number to show yet.


