What happens to each capture
- Apple’s Vision reads text, QR codes and barcodes, labels what the image shows and makes a visual fingerprint.
- Once the engine’s models are installed, each image also gets a vector from the same image and text model the Pool app searches with, so a search can match what a photo shows.
- A privacy gate holds back identity documents, payment cards, credentials and health records.
- Fixed rules turn what was read into links, objects and leads such as “a product page with a price”.
- On iOS 26 with Apple Intelligence, the system language model names the product, place, event, recipe, book, music, movie, article or post. Nothing leaves the phone.
- On devices without Apple’s model, that naming step can run through cloud assist when you set
namesWithCloudAssistand the person allows cloud assist. Otherwise those devices index without named things, and search, links and objects still work. Indexing never spends cloud assist units on a device that has Apple’s model.
Progress
status.phase is one of waitingForGrant, waitingForPermission, indexing, paused or idle. Indexing slows down while the device is hot or in Low Power Mode and keeps going.
In detail
status.detail says where a run is, in words you can show:
status.lastRun says how the last run ended: reason is completed, stopped (your app paused, or a grant changed during the run), endedByIOS, failed, interrupted (the app was closed) or waiting (on photo access, a grant or a refused key), with finishedAt, nextAttempt when PoolKit will try again by itself, and the indexed and failed counts of that run.
Show captures as they are indexed
batches(using:) streams newly indexed captures in slices of about a hundred, so you can fill a screen while the first index runs.
observe operation and covers only the sources in the grant. Each batch has an id (bat_…) and items with the SourceAsset id, the media type, the date and the labels. Labels are filled only when the grant also has evidence. Sensitive and out-of-focus captures are left out, and shots of one moment (same source, 10 seconds or less apart, near-identical images) appear once. The stream ends when the grant stops covering anything, for example when it is revoked.
Why it paused
paused means there is work left and Pool is not doing it. status.pauseReason is failed when listing, reading or storing went wrong, with a short failureDescription for your logs, or interrupted when a background task ran out of time. Pool starts again by itself in both cases. byApp means your app called pause().
The engine’s models
The models are about 375 MB. They are not in the package. PoolKit asks Pool’s service for the list with your app key, downloads the files after the first indexing pass has listed the library, and checks each one against its SHA-256 before using it. Until then search, queries, facets and recurring things work from text, labels and things.status.visuallyIndexedAssets counts the photos that already have a vector, and status.models carries the same state as the stream.
Choose what is indexed
IndexOptions says what the index covers and how it runs. Set it when you open the client, and change it at any time.
Changes apply in place, and the next pass starts at once:
- Turning
placesoff deletes every coordinate, city and visit straight away. - A new
focushides or reads again what it changes. Switching back to.everythingreads the rest. - A new
windowis anchored from now.
.everything to keep that. PoolConfiguration.indexWindow and maximumAssets still work and set the same options.
Pause and resume
While the app is closed
Register the background tasks once, before the app finishes launching:BGTaskSchedulerPermittedIdentifiers in Info.plist, and add processing to UIBackgroundModes. PoolKit uses only the identifiers that are listed.
nil identifier, registerBackgroundTasks() reads it from the PoolKitBackgroundTaskIdentifier Info.plist key. It returns false when iOS refuses the registration, for example when an identifier is not listed. Registering does not need a client: when a task starts, it waits up to 20 seconds for your app to open one, and otherwise asks again in 10 minutes. So open the client early in launch. pool.sources.registerBackgroundIndexing(identifier:) on an open client still works and does the same.
How it runs:
- Leaving the app during the first index. On iOS 26, PoolKit asks iOS to continue the work and iOS shows
IndexOptions.backgroundProgress: your title, and “n of m” or your own subtitle. Before iOS 26, or when iOS says no, PoolKit asks for the extra time iOS grants to finish the slice it is reading.status.detail.backgroundiscontinuingin both cases. - First index unfinished. The next processing run is requested a minute out, and
detail.backgroundisprocessingTaskwhile it runs. - Once indexed. Update runs are requested every 15 minutes. iOS decides when they actually run.
- Ended by iOS.
detail.backgroundandlastRun.reasonareendedByIOS, and the next run carries on where this one stopped. - Failed. The next background attempt waits 10 minutes.
pool.sources.indexNow() from your own task.
Storage
The index is a SQLite file in your app’s Application Support folder, protected by iOS file protection and excluded from backups. It holds derived text, labels, fingerprints and vectors, never images. The models are stored next to it.pool.sources.rebuildIndex() deletes and rebuilds the index and keeps the grants.
deleteIndex() deletes the index and everything derived from the captures, and keeps the grants, the index options and the downloaded models. While a grant is active, indexing starts again from the sources; call pause() first to keep it empty.

