Skip to content

0013 — ONNX Runtime auto-provisioning & bundling

Status: IN PROGRESS (2026-07-05) — Slice 1 (auto-provisioning) & Slice 3 (config/UI/docs) landed and verified live (a removal with no path configured auto-downloaded ORT and inpainted); Slice 2 (bundling the runtime in the macOS .app) rides 0010's packaging work, and auto-download already covers macOS in the meantime. Remove the last manual step from the local ML capabilities: make the ONNX Runtime shared library arrive automatically, the way the models already do, so enabling eye detection or object removal never asks the user to find, download, or path-in libonnxruntime. Companion to 0004 (face.*), 0008 (remove.*), 0010 (the macOS app that bundles it), and the master ML-runtime open question (0001 §13-Q1).

§7 resolved (2026-07-05, Matt): do both — bundle the runtime in the shipped macOS app and auto-download everywhere else. Implementation may begin.

Why now. Testing in the studio (2026-07-05) surfaced the gap: enabling eye detection produced "load onnxruntime: libonnxruntime.so: cannot open shared object file". The models auto-download and checksum-verify (pkg/face/models), but the runtime library was deliberately left manual — a code comment reads "The ORT shared library is not auto-fetched — it's platform-specific and stays config-supplied via face.library_path." For a photographer's tool that is the wrong trade: it demands the one piece of platform knowledge Hailey should never need, gives a cryptic error when absent, and must be set twice (face.library_path and remove.library_path).


1. Goals

  1. Zero manual runtime path. With eye detection or object removal enabled and no path configured, krites obtains a working libonnxruntime on its own.
  2. One resolution, shared by both providers — face and remove resolve the same library; the path is never configured twice.
  3. Bundled on the shipped Mac app (0010) so Hailey's machine needs no download at all — the dylib ships inside the .app.
  4. Auto-download fallback (download → extract → checksum-verify → cache) for the CLI and any unbundled platform, mirroring the model fetcher.
  5. library_path stays as an advanced override, not a requirement.

Non-goals

  • No CGO. The shota3506/onnxruntime-purego binding dlopens the lib at runtime; this spec only decides which path it dlopens (0004 R-FACE-2).
  • No change to model provisioning (already automatic).
  • Windows support is out of scope for the first cut (Hailey is macOS; dev is Linux) — the resolver errors clearly there, pointing at library_path.

2. Resolution order

A single resolver returns the library path, trying in order (R-ORT-1):

  1. Explicit overrideonnxruntime.library_path (new, shared), or the legacy face.library_path / remove.library_path (still honoured). Non-empty wins, unconditionally — the escape hatch for a hand-built or system lib.
  2. Bundled — a lib shipped alongside the executable (the macOS .app, §4). Located relative to the binary, so it moves with the app.
  3. Auto-provisioned — the pinned ORT release for the current platform, downloaded, extracted, checksum-verified, and cached (§3). Fetched once.

The resolved path is passed into onnx.Config.LibraryPath by both providers, so the onnx adapters and the binding are unchanged.


3. Auto-provisioning (the fallback)

R-ORT-2 — pinned, per-platform, checksum-verified. A pinned ONNX Runtime version (providing C-API ≥ 23, the binding's target) maps GOOS/GOARCH to Microsoft's official release archive:

Platform Asset (example, version pinned in code)
linux/amd64 onnxruntime-linux-x64-<v>.tgz
linux/arm64 onnxruntime-linux-aarch64-<v>.tgz
darwin/arm64 onnxruntime-osx-arm64-<v>.tgz
darwin/amd64 onnxruntime-osx-x86_64-<v>.tgz

The archive is downloaded to a temp file, its SHA-256 verified against a pinned per-platform hash (refusing a mismatch), the lib/libonnxruntime.* entry extracted into the cache ($XDG_CACHE_HOME/krites/runtime/<v>/), and its path returned. A subsequent run finds the cached lib and skips the download (R-ORT-3). A platform whose checksum is not yet pinned fails with a clear message naming library_path — never an unverified download (R-ORT-4).

Reuse. This mirrors pkg/face/models (verify → fetch → cache) but adds archive extraction (tar.gz via stdlib archive/tar + compress/gzip). ORT is MIT-licensed, so download, cache, and redistribution are all unrestricted (unlike the Places2 model caveat, 0008 §4.1).


4. Bundling in the macOS app (0010)

R-ORT-5. The goreleaser .app build embeds the platform libonnxruntime.dylib (+ its MIT LICENSE) inside the bundle (Contents/Frameworks or beside the binary), and the resolver's bundled step (§2.2) finds it there. So a double-click install has the runtime present from first launch — auto-download in §3 never runs on Hailey's Mac. This is additive to 0010's packaging slice.


5. Config & UI

R-ORT-6. A new shared key onnxruntime.library_path overrides the resolver for both providers; face.library_path / remove.library_path remain as per-provider overrides for back-compat but are no longer required. In the studio Settings panel the runtime path becomes an optional "Advanced" field, not a prerequisite — enabling a capability no longer footguns the user into the missing-library error (the resolver supplies it).


6. Docs (Definition of Done)

  • Update the face/eye and object-removal how-tos: enabling them "just works"; no library path needed. The config reference marks *.library_path optional and documents onnxruntime.library_path + the resolution order.
  • A short explanation of where the runtime comes from (bundled vs downloaded).

7. Open questions

  • Q1 — bundle vs download vs both.RESOLVED (2026-07-05, Matt): both. Bundle in the shipped Mac app; auto-download for CLI/other platforms.
  • Q2 — which platforms to pin now. Recommend: pin every checksum we can fetch and verify at implementation time (Linux x64 is the dev box; macOS arm64 is Hailey's target and also bundled). Gate unpinned platforms with the §3 error. Confirm.
  • Q3 — ORT version pin & cadence. Pin one version providing C-API 23; bump deliberately (like the model pins). Mirror to an artefact we control before a released build (the same hardening TODO the model pins carry).

8. Slices

  • Slice 1 — the resolver + wiring.LANDED (2026-07-05). pkg/onnxruntime: resolution order (override → bundled → auto-download), all four Linux/macOS platforms pinned against ORT 1.23.0, download+extract(tar.gz)+checksum+cache. faceprovider + inpaintprovider resolve through it; shared onnxruntime.library_path. Unit-tested (order, extract, checksum-mismatch) + a real network download (INT_TEST). Verified live: a studio removal with no path configured auto-provisioned ORT and inpainted.
  • Slice 2 — macOS bundle.Rides 0010. The bundled-path resolution step is done and tested; embedding the dylib in the .app via goreleaser lands with 0010's packaging slice (no Mac build here yet). Auto-download covers macOS until then.
  • Slice 3 — config/UI/docs.LANDED (2026-07-05). Panel library-path fields marked optional ("downloaded automatically — leave blank"); config reference gains an ONNX Runtime section + onnxruntime.library_path and marks the legacy per-provider paths optional; the face/eye and object-removal how-tos note the runtime auto-provisions. Enabling a capability no longer requires a path.