Skip to content

Architecture

Three processes and one SQLite file.

System architectureone control plane · N workers · any network
public internet →clientmacbatch benchor your scriptPOST /v1/jobssubmit itemspoll → resultscontrol planeFastAPI · one processjobs → shards → taskspending · leased · done · failedlease table + reclaimexpiry 600s → back to pendingworker registryheartbeats · stale after 60sSQLite · WAL · ~/.macbatch/control-plane no scheduler thread — leases reclaimed on request worker · your Macmacbatch serve · localhostOllama :11434worker · friend Macmacbatch serve · Cloudflare tunnelOllama :11434worker · any Macmacbatch serve · HTTPSOllama :11434 workers pull ⟵ lease · heartbeat · complete_batch
The dashed line is the only trust boundary that matters: everything right of it is a machine you may not control, running work you did not verify.

Workers pull, the control plane never pushes

This is the single decision the rest of the design falls out of.

A worker opens outbound HTTPS to the control plane to heartbeat, lease, and complete. The control plane holds no connection to any worker and cannot initiate contact. So a Mac behind home NAT, on hotel Wi-Fi, or on a phone hotspot joins the pool with no port forwarding, no VPN, and no static address.

The cost is that the control plane cannot dispatch. It can only wait to be asked, which is why leasing is greedy and why a fast worker can starve a slow one.

Control plane

One FastAPI app, macbatch.control.app. Endpoints are thin; all state lives in SQLite.

TableHolds
jobsOne row per submitted job, with meta_json carrying item and shard counts
tasksOne row per shard: payload, status, attempts, lease, result, error
workersRegistry keyed by worker id, with heartbeat time and cumulative counters
task_eventsAppend-only audit: leased, done, failed, requeued, lease_expired

There is no scheduler thread. Expired leases are reclaimed inside request handlers — reclaim_expired_leases() runs at the top of lease, heartbeat, job read, and worker list. An idle control plane reclaims nothing, which is fine, because the next poll does it.

Job status is likewise derived rather than tracked: reading GET /v1/jobs/{id} counts task statuses and flips the job to completed or partial when nothing is pending or leased.

Worker

macbatch serve runs WorkerAgent. Per cycle: heartbeat, lease up to lease_limit shards, execute them (optionally across max_concurrent threads), write each result to local disk, then bulk-post everything in one complete_batch request.

The local write before upload is deliberate. Compute on a consumer laptop is expensive enough that losing a finished shard to a dropped connection is worth avoiding, even though the current release does not yet replay those files automatically.

Execution dispatches on a fixed allowlist — embed, ocr, classify, generate — each mapping to a specific Ollama call. There is no path by which a control plane can make a worker run arbitrary code.

Runtime boundary

macbatch schedules; Ollama infers. The worker talks to http://127.0.0.1:11434 and knows only two shapes of call: /api/embeddings for embed work and /api/generate (optionally with base64 images) for everything else.

Swapping in MLX or another runtime means implementing those two calls behind the same interface. Nothing above the worker changes.

What is deliberately absent

MissingWhy
AuthenticationNot built yet. A token config field exists and is unused.
MigrationsSchema is CREATE TABLE IF NOT EXISTS; existing databases are never altered.
Connection poolingOne SQLite connection per request, WAL mode.
Task prioritiesTasks are leased in rowid order — strict FIFO across all jobs.
Scheduling fairnessFirst worker to ask gets the work. This produces measurable skew.
Result replayLocally-stored shard results are not re-uploaded after a failed gather.

MIT licensed. Every benchmark on this site is reproducible with macbatch bench.