everOS: A Reproducible Yocto Build for Pet-Health Edge Devices
A pet-health monitor doesn't get to reboot when it feels like it. It sits on a kitchen floor, watches a bowl, weighs every meal, listens for chewing, runs computer-vision models on-device, and uploads insights — continuously, for months, unattended. The Linux underneath all of that is not a detail. It is the product's reliability.
For our first field units, that OS was a hand-assembled image: a stock Linux distribution with binaries copied in by hand and service files dropped into place one SSH session at a time. It worked. It also wasn't something you could rebuild, audit, or ship to a thousand devices the same way twice. This post is about replacing it with everOS — a purpose-built Yocto Linux that is reproducible from source, updates over the air, and recovers itself when a bad update lands.

The Problem: a build you can't rebuild
A hand-built image has three quiet failure modes. First, it isn't reproducible — nobody can regenerate the exact same OS six months later, because the "recipe" lives in someone's memory and shell history. Second, it doesn't scale — every new unit inherits the same manual steps, and every manual step is a chance to ship a slightly different device. Third, it can't update safely in the field — there's no clean way to push a new build and roll back if it bricks.
For a device that lives in a customer's home and makes health observations about their pet, all three are unacceptable. We needed the operating system itself to be an artifact — declared in code, built by a server, and identical on every unit.
The Approach: a custom Yocto distro and machine
Yocto builds an entire Linux distribution from source according to recipes — small declarative files that say how to fetch, compile, and package each component. We created our own layer, meta-everos, holding a custom distro definition, a machine definition for our compute module, and recipes for every one of our services.
The build emits a single image with an A/B partition layout: a boot partition, two full root-filesystem slots (A and B), and a persistent data partition. Updates flash the inactive slot and switch over on the next boot — so a failed update never touches the system you're currently running on. That structure is the foundation everything else hangs from.

The Process: porting real services to a cross-compiled world
Moving from a hand-installed image to a from-source build surfaces every hidden assumption. The hard part isn't writing recipes — it's the fixes you only discover when something that "just worked" on the old image refuses to build or boot on the new one. Three of those fixes are worth showing in full, because they're the difference between a demo and a device.
Fix 1 — the binary that linked against the wrong world
One of our most important binaries is a single monolithic program that handles the weight, audio, and a sensor-module trigger path. On the old image it was a prebuilt binary copied straight in. Under Yocto it failed immediately: the prebuilt version was linked against the previous distribution's networking library, whose versioned symbols simply don't exist in a from-source build. A binary that links against symbols your OS doesn't ship is a runtime crash waiting to happen.
The right answer was to cross-compile it from source inside the recipe, so it links against everOS's own libraries. That exposed a second, smaller trap: the source includes headers via a ./cm4/... path, but in our build the repository is cm4, so that path didn't resolve. The fix is a one-line self-symlink:
recipes-hoomanely/cm4-firmware/cm4-firmware_git.bb
52 do_compile:append() {
53 cd ${S}
54 # weight/80sps_api_auto_tare.c includes "./cm4/<hdr>"; the repo IS cm4,
55 # so a self-symlink makes ./cm4/X resolve to ./X.
56 ln -sfn . ${S}/cm4
57 ${CC} ${CFLAGS} -I${S} -O2 -Wall \
58 weight/80sps_api_auto_tare.c vbus.c can_handler.c mpack.c vbus_audio_receiver.c \
59 ${LDFLAGS} -lgpiod -lsqlite3 -lpthread -lm -lcurl \
60 -o ${S}/weight_continous
61 }
That self-symlink makes ./cm4/header.h resolve back to ./header.h without touching a single source file. The result is a binary linked against everOS's own libraries — no missing versioned symbols, no runtime surprise.
Fix 2 — when the build host's identity leaks into the image
Our on-device machine-learning stack depends on a large C++ tensor runtime, which we package from an official prebuilt distribution. The first builds tripped a packaging quality check complaining about an unknown user ID. The cause was subtle: copying the unpacked files preserved the build host's ownership (the developer's user ID, 1000) instead of resetting it to root. An image where files are owned by a user that doesn't exist on the device is exactly the kind of non-reproducible artifact we were trying to eliminate.
The fix lives in the recipe's install step — copy in a way that resets ownership, then explicitly normalize everything to root:
recipes-support/libtorch/libtorch_2.5.1.bb
42 # Headers (install resets ownership; cp -R would carry host uid 1000).
43 cp -R ${S}/torch/include/* ${D}${includedir}/
...
49 # Reset host (uid 1000) ownership from the unpacked wheel to root.
50 chown -R root:root ${D}
51 }
It's two lines of intent, but it's the line between "builds on my machine" and "builds identically anywhere."

Fix 3 — the one-line root cause of every OTA failure
The most expensive bug to find was the cheapest to fix. Over-the-air updates kept producing devices that came up "alive but wrong": persistent configuration missing, update scripts editing a phantom empty directory, rollback silently doing nothing. We chased it through the update logic for a while before realizing the update logic was fine.
The root cause was the filesystem table. The image-creation tool injects the boot and data mount entries only into the flashed image — but an OTA writes a bare root filesystem into the standby slot, which never had those entries. So freshly-updated slots simply didn't mount /boot or /data. Everything downstream — device identity, calibration, the rollback mechanism that edits boot files — failed for one reason: the partitions weren't mounted. The fix puts the mounts in the root filesystem itself, so every slot, flashed or OTA-written, mounts them:
recipes-core/base-files/base-files_%.bbappend
5 do_install:append() {
6 install -d ${D}/boot ${D}/data
7 cat >> ${D}${sysconfdir}/fstab <<'FSTAB'
8 LABEL=boot /boot vfat defaults,nofail 0 2
9 LABEL=data /data ext4 defaults,noatime,nofail 0 2
10 FSTAB
11 }
Two lines added to the filesystem table, and the entire A/B update-and-rollback story started working as designed.
Why It Matters at Hoomanely
Hoomanely is reinventing pet healthcare through Physical Intelligence — the idea that an animal's health shows up first in physical behavior: how much it eats, how it moves, the sounds it makes, subtle changes day over day. Our devices capture that behavior at the edge and feed our Biosense AI Engine, which turns continuous observation into early, preventive health signals long before a problem becomes a vet visit.
None of that works on an OS you can't trust to run for months and update without bricking. everOS is the platform that earns that trust. The reproducible build means every device in the field is provably the same. The A/B layout with a persistent data partition means a device keeps its identity and calibration through every update and can always fall back to a known-good slot. The cross-compiled service binaries mean the sensing, vision, and audio pipelines link against the exact libraries we shipped — not whatever happened to be on a developer's laptop.
In short, the unglamorous work in this post — symlinks, ownership resets, two lines in a filesystem table — is what lets the glamorous work, continuous AI-driven pet health monitoring, run reliably in someone's home.

The Results
The payoff is concrete. A full image now builds from source on our build server with every service, model, and configuration declared in meta-everos. We flash it to a unit by exposing the module's storage to a laptop and writing the image directly — a few minutes, repeatable, no manual post-install steps. The device boots into slot A with all services up in seconds, mounts its persistent data partition, and reads its identity from a config file there.
We also caught a real provisioning gap in the process: a brand-new flash leaves the data partition empty, so the device boots with no identity until that config is written. Discovering that on the bench — rather than in a customer's home — is exactly what a reproducible, inspectable build is for. It's now tracked as a build step so future flashes are provisioned automatically.
Key Takeaways
- Treat the OS as an artifact, not a setup procedure. A from-source Yocto build makes every device provably identical and re-creatable months later.
- Cross-compile, don't copy. Prebuilt binaries silently depend on the OS they were built on; rebuilding from source against your own libraries removes a whole class of runtime failures.
- Reproducibility is in the boring details. A leaked build-host user ID or a missing filesystem-table entry can break an image as thoroughly as a logic bug — and they're invisible until you look.
- Design updates to be reversible from day one. A/B slots plus a persistent data partition turn a risky field update into a safe, recoverable operation.
- Find provisioning gaps on the bench. An inspectable build surfaces "it only worked because state persisted" assumptions before they reach a customer.
Author's Note
I'm a firmware engineer at Hoomanely, where we're building AI-powered devices for preventive pet care. everOS is the Linux platform our edge units run on — the layer that has to stay up, update cleanly, and recover on its own so the Biosense AI Engine can do its job: watching for the small physical changes that mean a pet needs attention, before it becomes an emergency. The fixes here are small. The standard they hold the platform to is not.