Skip to content

Why the heavy providers are opt-in

Face and eye analysis, object removal and AI review all ship off. Turning each on is a separate, deliberate act. This page explains why that is the default rather than a setting nobody got round to flipping.

What is off by default

Capability Config key What it costs to turn on
Face / eye / expression analysis face.enabled A local ONNX model download, and a model pass per frame
Object removal remove.enabled + remove.accept_licence_risk A local ONNX model download, and an explicit licence acceptance
AI review review.provider Your images leave the machine, and you pay per call
Diagnostics /metrics diagnostics.metrics.enabled An extra endpoint on the studio

With all of them off, a cull is model-free: sharpness, exposure and near-duplicate detection are deterministic maths over the pixels, and krites never loads a model or opens a socket.

The three reasons, and they are different reasons

Lumping these together as "privacy" gets it wrong. Each capability is off for its own reason, and knowing which one applies tells you what turning it on actually costs.

AI review is off because the images leave

This is the privacy case, and it is the strongest. Review sends the full image to a third-party model. For a wedding photographer that is somebody else's wedding, sent to a company they never chose, under terms they never read.

So there is no default provider and no ambient key. You pick a provider, you enter its key in the studio — where it goes to the OS keychain rather than the config file or an environment variable — and krites discloses the egress before the first image is sent, and again above ten frames.

A default that silently uploaded photographs would be a betrayal, not a convenience. There is no version of "off by default" here that is too cautious.

Face analysis is off because it is expensive and imperfect

Face and eye analysis runs entirely on your machine — local ONNX models, nothing leaves. So the reason it is off is not privacy; it is cost and honesty.

Cost: it adds a detection pass and a landmark pass per frame, over thousands of frames. Somebody culling landscapes or details would pay that for nothing.

Honesty: an eye-open probability is an estimate, and an estimate that moves a verdict needs to be worth trusting. That is also why the hard eye gate is off even when the provider is on — a closed-eye frame is never auto-rejected, because it may be the only record of a moment. krites proposes; you dispose. Making the model opt-in keeps its influence something you chose rather than something you discovered.

The expression signals go further: smile and facing-camera ship verdict-neutral, informing which frame of a burst is preferred but never demoting one. A solemn ceremony face is normal at a wedding, not a defect. And the geometry behind them is not yet calibrated against a real-frame distribution the way the eye anchors are — an uncalibrated estimator can inform a ranking, but it must not move a verdict.

Object removal is off because of the model's licence

Both inpaint backends are trained on Places2, whose terms are non-commercial and no-redistribution. That is a problem you inherit if you deliver a client's photos with a generative fill in them.

So removal sits behind a hard gate, not a soft default: remove.accept_licence_risk must record explicit acceptance before the inpainter is ever constructed. Both that and remove.enabled must be true. The studio surfaces the disclosure and the acceptance; the CLI declines with the same message until it is set.

This is the one case where "off by default" is not really about defaults at all. It is a consent record.

The shape this forces on the code

Every heavy backend sits behind a narrow Go interface chosen from config: a decoder, a face analyser, an aesthetic scorer, an inpainter, a burst clusterer. Each adapter is its own package, and the deterministic engine never imports one.

That is not architecture for its own sake. It is what makes "off" a real state rather than a flag that skips a call. With the provider disabled, the native code is not loaded at all — and the engine that remains is pure Go with no cgo, which is why it compiles to WebAssembly and why a cull with everything off behaves identically on every platform.

It also makes adding a backend purely additive: a new adapter behind the same interface, with no call-site changes. face.detector switching between two face detectors, and remove.backend between two inpainters, are both that seam showing.

What "local-first" does and does not promise

Does: with the defaults, nothing about your photographs leaves your machine. The cull is local, the develop is local, the export is local, the studio binds loopback and refuses anything else.

Does not: it is not a claim that krites never talks to the network. It checks for updates against the release source, fetches and checksum-verifies model files the first time you enable a capability that needs them, and resolves the release signing key over Web Key Directory. None of those carry your images.

The line is about your photographs, and it is drawn per capability, with disclosure at the point of crossing.