My wife had been asking me for the photographs from our son Samuel’s baptism in 2014.

They were backed up. Somewhere. Along with old phone copies, camera imports, OneDrive folders and directories carried forward from computers I stopped using years ago. I had preserved almost everything, but that did not mean any of it was particularly findable.

I think I had been looking for the usefulness of a personal AI assistant in the wrong places.

I use AI constantly now, although most of that use still looks like work. I discuss architecture and ideas with it, use Codex and Claude Code directly, and have used a friend’s code factory, where agents help shape work, implement it and review the result. I have already written two posts about that side of it.

In all those cases I know roughly what I want to accomplish. AI helps me reason about it or carry out the work. A personal assistant is a less obvious proposition. I have wanted a more persistent one for a long time, but without the usual sales, marketing or administrative workload I was never quite sure what I would ask it to do.

I now have Hermes running at home. It knows Samuel’s school schedule and lunch menu, so my wife can ask through WhatsApp what his school day looks like and what he is having for lunch. That is genuinely useful, but still a fairly narrow example. I do not have a calendar full of meetings that somebody else needs to arrange, and I have little interest in letting an AI compose routine email on my behalf.

Then I looked at the storage box full of old photographs.

A task I could do, but probably never would

The problem was not that I lacked the technical ability to organise them. I know how to compare hashes, extract EXIF metadata and write scripts that move files into directories by date. I could deploy a photo application as well.

The deterministic parts were never really the obstacle.

The archive had accumulated through years of sensible decisions made one at a time. Copy a phone before replacing it. Preserve a Windows profile before reinstalling the computer. Save the camera import in two places. Keep an old OneDrive folder because it might contain something missing elsewhere. Each action was reasonable. Taken together, they had produced a collection spread across overlapping directories, old backups and several generations of devices.

Doing it properly meant answering thousands of small questions. Which trees belonged in the collection? Which identical copy should survive? Were two similar photographs duplicates or consecutive frames from a camera burst? Was an impossible date caused by a broken camera clock? Could a missing date be recovered from a filename or its surrounding directory? Which files were photographs at all?

None of those questions was especially difficult on its own. There were simply too many of them. It was the sort of job that gets postponed because the manual parts would be tedious beyond measure, even if scripts handled much of the bulk processing.

Working through the pile

I used two agents with different responsibilities. One worked locally on the storage host and investigated the photo collection. The other handled the infrastructure around Immich and made the result available to the family.

The first discovery pass found more than 148,000 image and raw-camera files across the main backup and photo areas we searched. That was a broad candidate count and included a great deal of overlap. From the source trees selected for intake, just over 50,000 media files, representing almost 600 GiB, were copied into a separate working dataset. The original directories were left untouched.

The first duplicate pass was straightforward byte-level work. It found 18,732 exact duplicate files and removed about 235 GiB from the working copy. That still left more than 31,000 files.

The next stages were less mechanical. About 4,400 files lacked a reliable embedded date. Dates had to be recovered from camera filenames, messaging exports, dated folders and the context surrounding each file. Some formats could accept corrected metadata; others needed a filesystem timestamp as a fallback. When only a date was known, we used noon so timezone conversion would not quietly move the photograph to the previous day.

There were also camera clocks that had gone badly wrong. Photographs from an old LG U880 phone claimed to have been taken in February 1980, decades before the phone existed. Another camera had scattered photographs into the years 2037, 2040, 2068 and 2097.

Those were not problems a hash comparison could solve. We used the camera model, firmware dates, filenames, neighbouring images, old directory names and known event dates to reconstruct what had probably happened. Where the evidence supported a correction, we wrote it back. Where it did not, the file remained undated.

Visual duplicate detection needed similar restraint. Two files can contain the same decoded image despite different compression or metadata, but two near-identical photographs can also be distinct frames worth keeping. The comparison combined hashes, perceptual fingerprints, filenames, timestamps, dimensions and sizes. It removed another 6,618 strong duplicate matches while deliberately retaining 684 ambiguous images.

Every destructive stage began with a report or dry run. The working dataset sat on ZFS, so we also created snapshots before material changes. Deletions and corrections happened only inside that new dataset. The scattered source backups, including the copies excluded during deduplication, remained unchanged.

Internet archaeology

The geolocation work became the most collaborative part of the project.

Some photographs already contained GPS data. Others were easy because I remembered the location. Then there were the difficult sequences: a shopping centre somewhere in Manila, a hotel that had since changed name, an attraction that no longer existed, or a series of ferry photographs with no obvious marker beyond the order in which they had been taken.

We compared signs, buildings and storefronts in the photographs with maps, historical business listings, event schedules and photographs other people had posted online. Old email helped identify the small resort where we stayed on Boracay in 2010. A visible shop and contemporary branch information helped distinguish Greenbelt 3 from other possible shopping centres. Camera-clock offsets, flight times and the absence of night operations at Caticlan helped identify the airport at the end of another sequence.

Concert photographs from an old HP camera required filenames, source directories and the 2003 Arvikafestivalen schedule.

I supplied memories when I had them, rejected wrong turns and recognised details that meant nothing to the model. The assistant could search patiently, compare weak pieces of evidence and keep track of the growing record. Some locations could be identified precisely. Others stayed labelled probable, approximate or generic. I did not try to add detail where the evidence was missing.

The scripts handled files. The assistant helped investigate what the files meant.

From backups to a family collection

While that work continued, the second agent set up Immich in one of my Kubernetes clusters (via Argo CD, of course). The curated archive was mounted read-only so the application could index it without becoming the authority for the source files. New uploads received a separate writable area. Authentication went through the same Keycloak setup used by the rest of the site, with accounts for the family.

With Immich, we could finally browse the photographs by date, location and face, search them, make albums and access everything from our phones.

And yes, we found Samuel’s baptism photographs.

Those mundane photographs may be the ones most easily lost. Weddings, baptisms and major trips remain in family memory even when their files are hard to locate. Everyday life disappears into directories called DCIM, Import or Backup_20070520.

Once everything was visible by date, place and person, looking for one event became a trip through years of our lives. I had forgotten that many of the photographs existed.

Until then, I had a large and rather thoroughly duplicated set of backups. I did not really have a family photo collection we could use.

Making it worth depending on

Once the family could actually use the collection, I wanted a better answer to what would happen if the storage box died. The old scattered backups still existed, but the newly organised collection and the Immich application state needed proper off-site copies.

I added daily off-site backups to Backblaze and tested restoring from them. The photographs and the Immich database have separate backup paths because they have different recovery requirements. I restored sample files from several parts of the archive and verified their hashes, then restored the database into an isolated PostgreSQL cluster to prove that the application state could be recovered as well.

That should have been the end of it.

Instead, tracing the recovery path for the photographs made me look rather more closely at how everything else was backed up.

But that became another project.