cookbook · personal · photo-grouper
Recipe · personal

Camera roll, grouped by event.

A local .kolm file that takes a batch of photos (with EXIF) and returns (album_name, event_type, who's_in_it, suggested_cover) for each cluster. Trained on 80 of your past albums you already named. Verifier rejects any album whose photos don't share a place + time window in the EXIF.

base modelphi-3-mini-vision
gold albums80 (56 train / 24 eval)
k-score floor0.80
artifact size3.4 GB
compile time~46 min
spec sourceEXIF-grounded clustering

What this recipe does

Apple's "Memories" decides what was a memory. This recipe lets you teach the model what counts as an album by feeding it 80 albums you already curated. It groups by EXIF time + GPS + on-device CLIP embedding similarity, then names the album in the style you used for the gold corpus ("brunch with the cousins" not "Sunday morning at 30°N").

Photos never leave your device. EXIF stripping happens after grouping — the artifact only sees the EXIF, not raw pixels, except through CLIP embeddings computed locally.

The spec

{
  "output_kind": "json",
  "schema": {
    "required": ["albums"],
    "properties": {
      "albums": { "type": "array", "items": {
        "required": ["name", "event_type", "photo_ids", "suggested_cover"],
        "properties": {
          "name": { "type": "string", "maxLength": 60 },
          "event_type": { "enum": ["meal", "trip", "work", "home", "event", "misc"] },
          "photo_ids": { "type": "array", "items": {"type":"string"} },
          "who": { "type": "array", "items": {"type":"string"} },
          "suggested_cover": { "type": "string" }
        }
      } }
    }
  },
  "verifier": {
    "album_must_share_time_window": true,
    "max_window_hours": 8,
    "album_must_share_place_or_subject": true,
    "name_style_corpus": "prior-albums.jsonl"
  }
}

Compile

kolm compile "camera roll grouper in my naming style" \
  --base phi-3-mini-vision \
  --pairs ~/.kolm/album-pairs.jsonl \
  --style-corpus ~/.kolm/prior-albums.jsonl \
  --verifier exif-grounded-clusters,style-faithful-names \
  --k-floor 0.80 \
  --output photo-grouper.kolm

ok wrote photo-grouper.kolm
   k_score=0.84  signature=hmac-sha256

K-score gate

K-score 0.84 held-out 24 albums · cluster-purity 0.91 · name agrees with held-out 78% (≥3-of-5 word overlap) · zero cross-event leaks

Run-time profile

M2 MacBook
3.1s
/100 photos
RTX 5090
680ms
/100 photos
iPhone 15 Pro
9.4s
/100 photos
CPU x86 (server)
12s
/100 photos

Deploy

# iOS shortcut — run on every "Photos batch import" event:
const photos = await Photos.getRecent({ since: lastRun });
const out = kolm.run('photo-grouper.kolm', photos.map(p => p.exif));
for (const a of out.albums) {
  await Photos.createAlbum(a.name, a.photo_ids, a.suggested_cover);
}