A running list of things I build for myself at home. They are not products and nothing here is for sale. They started as separate experiments and have turned into one small system I use every day, so I will start with the two pages where I actually meet it, then work back through the parts behind them.
Dashboards
Two pages I open every day
Almost everything below funnels into two screens. The first is a morning brief I read with coffee. It composes the day's training call, my schedule, a news digest, and the weather into one page, so I am not opening five apps before I am awake. A line of it might read: "Lift day, push focus, 40 minutes. Three things on the calendar, first at nine. Two items worth reading. 58 and clear."
The second is a fitness page, the workout selector's home, where the day's recommendation sits next to the reasoning that produced it, so I can argue with it instead of just following it.
Building the parts separately and composing them late turned out to be the right order. Each system stays small, testable, and replaceable on its own. The two pages hold no logic of their own, they just read from the parts and arrange them, so when I want to change what my morning looks like, I change the arrangement, not the machinery. Here are the parts.
Recommendations that have to earn it
Learns from passive behavior
Built with Python and SQLite, reading from Plex, Sonarr, TMDb, and Tautulli, with Claude doing the scoring.
Most recommenders treat every suggestion as a one-shot guess. I wanted one that earns trust over time, and only acts on its own once it has proven its picks were worth my time.
How it works
The taste profile is my existing library, which already encodes years of decisions about what is worth keeping. Candidates come from TMDb, a far larger catalog than I would ever browse. Claude scores each candidate against the profile from 1 to 10 and writes a one-line reason.
What it does with the score is the real design. Each source of recommendations climbs a trust ladder, trial to active to monitoring, and only earns the right to auto-add to my library once its earlier picks have actually been watched to the end. The completion signal comes from Tautulli, the playback layer, not from what I added to a list and never opened, because adding is aspirational and finishing is honest.
Example
A scored candidate comes back like this. "Slow Horses, 8 of 10. Matches your pull toward dry, low-glamour spy drama; nearest neighbor in your library is The Day of the Jackal. Holding on trial until something of its kind gets watched."
A score is a guess about one title. A ladder is a judgment about a source over time, and that is the more honest thing to let a machine act on by itself.
Teaching it by correcting it
Learns from human correction
Built with Python and SQLite, one Haiku call per new event, learning from a table of my own corrections.
Every calendar has events that mean something a machine cannot read off the title. I built a layer that turns each new event into structured meaning, and the interesting part is not the first guess, it is what happens when the guess is wrong.
How it works
Each new event gets one Haiku call that returns structured fields plus a confidence score between 0 and 1, and that score does real work. Above 0.85 it resolves on its own. Between 0.6 and 0.85 it waits for me to confirm or fix. Below 0.6 it is still saved and flagged, never dropped, because a wrong guess I can correct beats an event I never see.
When I do fix one, the correction is stored, and recent corrections get fed back into the next Haiku call as examples, so it adapts to my patterns rather than getting generically better. Recurring events inherit the resolution through a stable identifier on the series, so the model runs once per series instead of once per occurrence.
Example
In practice a recurring event that keeps getting mistagged needs only one correction. The fix propagates to the whole series, and it joins the handful of examples the model sees on the next call, so that shape of event stops getting it wrong.
The cheapest way to make a model fit your world is to let it be wrong, correct it, and have it remember. It is the same human-in-the-loop pattern that makes AI trustworthy in work that actually matters.
Knowing your own normal
Learns from a personal baseline
Built with Python and SQLite, pulling from Apple Health, Strava, and Hevy.
Most health alerts fire on absolute thresholds, a number some population study decided was high or low. That is noisy, because my normal is not the population's normal. This system learns my baseline from my own recent data and only speaks up when a change is real.
How it works
For each metric it keeps a rolling 28-day window of my own history and asks whether today sits inside or outside my normal. The output is one of four tiers, normal, elevated, possibly fighting something, and likely sick, so the rest of my setup can act on a single signal instead of a wall of numbers.
Most of the work was in the gates, not the model. A reading has to stay off for about four hours before it counts, which kills single-bad-reading noise. An elevated heart rate right after a hard workout is explained away by that workout's own intensity score rather than read as illness. A drop in blood oxygen is the one signal allowed to escalate the tier quickly, because there a real change should not wait.
Example
On a normal day it just says normal and stays quiet. After two short nights with a resting heart rate drifting up, it moves to "possibly fighting something" and quietly tells the workout selector to ease off before I have noticed anything myself.
Personal baselines beat population thresholds for anything that varies person to person, and the gate that turns a reading into an alert matters more than the reading.
The workout that fits the day
Reasons across the whole picture
Built with Python and SQLite, one Claude call a day over a packet the system assembles first.
Deciding what to train on a given day is a negotiation, not a question. I have running goals and lifting goals competing for the same recovery, and some days the honest answer is to rest. The inputs that should drive that call are scattered across everything else here.
How it works
Before any model runs, plain queries assemble a tight packet: the last seven days of training summarized, an acute-versus-chronic load read, the current health tier from the system above, the day's weather, the equipment on hand, and how far I am from each goal. That packet, not raw data, is all Claude sees. It is small and factual, which keeps the reasoning grounded and the cost near nothing.
The two-level fatigue read is the input I lean on most. Acute load catches the cooked-today case. Chronic load catches the opposite failure, ramping up too fast or letting fitness slide, which a single day never shows. Competing goals are handed over as inputs, not rules, so a goal can bend around a bad week instead of a rule breaking the first time I am sick.
Example
A morning reads. "Lift, push focus, 40 minutes. You ran hard twice this week and your legs need a day. Rain clears tonight, so save the easy run for tomorrow, when the half-marathon block actually needs it."
The hard part is never a single signal, it is holding them against each other. A formula adds numbers up. Deciding that tired legs and a wet forecast outweigh a plan that says run today is judgment, and that is the part worth handing to a model.
A morning brief that filters itself
Reads the firehose so I don't have to
Built with Python, FastAPI, and SQLite, with two Claude models doing different jobs.
I follow six legal and AI sources. On a busy week that is well over a hundred headlines, and most of them do not matter to me in particular. So a pipeline reads them every couple of hours, scores each one against my actual work, and leaves a few lines on my morning page.
How it works
Plain string filters drop the obvious junk before any model runs, sponsored posts, duplicates, anything published before the last fetch. Noticing an ad is not worth a model call.
What survives gets scored by Haiku, the small fast model, on two axes from 1 to 10. Novelty is measured against what I have already seen this week, so yesterday's story rewritten drops out. Relevance is measured against me in particular: my work running knowledge and innovation at a law firm, the legal industry I sit in, the peer firms I keep an eye on, and the specific products, vendors, and models my teams might actually touch. A generic AI headline scores middling; the same development attached to a tool we use, or a move by a firm in our space, scores high. Once a day Sonnet, the slower model, writes a two or three line brief over the dozen or so top-scoring items, the only expensive call in the whole thing.
Those relevance rules live in a config file, not in the model and not in code. The file is where my world is written down, the topics, the peer firms, the vendors and models worth flagging. That is also what makes it easy to retune: adding a source or changing what counts as signal is a one-line edit, not a deploy.
Example
A morning line might read. "Two firms published client-facing GenAI policies this week; one ties model use to its existing outside-counsel guidelines, which is the angle worth watching." That is the entire interaction. No feed, no inbox, no skimming.
The whole pipeline costs a couple of dollars a month. The pattern under it is the one I reuse everywhere: cheap code throws out the obvious, a cheap model sorts at volume, and the expensive model is spent only on the final synthesis.
Monitoring that only speaks up when something is wrong
Reports failure, not health
Built with Python and SQLite, publishing over MQTT, with a small set of probes on a timer.
I went looking for one broken thing and found four. A radio bridge had been down for over a week, and a hundred sensors behind it had been reporting nothing at all. A service had been posting to a machine I decommissioned months earlier. Some exterior lights had been on for five days. A print sat paused at temperature with nobody watching it. Every one of those had been true for days, and nothing had told me.
The reason was not that monitoring was missing. It was that everything reported health, and health is easy to fake. A service that is running is not the same as a service that is working, and a page of green ticks is a page nobody reads.
How it works
Probes run on a timer and write observations onto one bus. Almost all of them produce nothing. A probe that finds the world in order says so quietly and moves on, so the only things that surface are exceptions, each with a severity and a plain sentence saying what is wrong and for how long.
The rule that made it useful is that severity decides who gets interrupted. Something actually broken becomes an active concern and reaches my phone. Something merely worth knowing goes into a feed I can ignore. Diluting the first category with news would have made the whole thing worthless within a week, which is the failure state of every dashboard I have ever abandoned.
The other rule is to alert on the age of the artifact, never on the status of the job that makes it. Backups are the clearest case. The job reported success for months while producing nothing, because one unreadable file made it exit in a way that looked like completion. Checking whether the newest backup is less than thirty hours old cannot be fooled that way. It asks about the thing I actually want, not about the machinery that is supposed to produce it.
One finding was worth the whole exercise. I had assumed a timestamp saying when something last changed was the same as how long it had been that way. It is not, because those timestamps reset when the system restarts. The lights that had been on for days reported sixteen hours, which means a naive watchdog would have been wrong in exactly the case it existed for.
Example
A quiet week produces nothing at all. A bad one produces a line like this. Backups, newest snapshot is 41 hours old. That is the entire notification. If I am not being told anything, there is nothing to look at, and I have learned to trust that.
A monitor that always shows something trains you to stop looking, so the only honest design is one that stays silent. The question worth asking of any check is what it would take for this to read green while the thing it watches is broken. Every failure above had an answer I did not like.
Rooms that admit when they cannot tell
Reports evidence, not assumptions
Built on battery radar and motion sensors, composed in Home Assistant around a rule about what counts as evidence.
My first version of room presence was mostly confident guessing. If a person was home and it was late, their bedroom reported occupied. That is an assumption wearing a measurement's clothes, and it got caught the evening it reported a bedroom occupied while both of us sat downstairs.
So I rebuilt it around one rule. A room answers exactly one question, whether there is evidence of a person in it right now, and it never infers a room from the fact that somebody is in the building.
How it works
Every room carries a short description of what it is actually measuring, and that description travels with the answer. Some rooms have radar and motion and a thermostat that notices people. Some have only a media player, which cannot detect a quiet person at all. That description is what lets a reader tell empty from unmeasured, and those are completely different facts.
Where a signal is known to lie in a particular direction, it says so rather than being quietly corrected. One sensor holds occupied long after a room empties, so its contribution is labelled as sticky, and you can see when it is the only thing making a claim. I left it in because removing it would trade false positives for false negatives, and a room going dark while someone sits in it is the worse failure.
Getting the timing right mattered more than adding sensors. A ten minute hold was sized for motion sensors, which go blind the moment you stop moving. Radar holds a still person by design, so on those rooms the long hold bought nothing and made walked through nine minutes ago look identical to standing there now.
Example
Checked against what was actually happening in the house, it got six rooms out of eight right. One of the misses is permanent and I kept it. A room with only a media player reported nothing while a kid sat reading in it. That is the honest answer for the instruments in that room, and the fix is a sensor, not a better guess.
The temptation is always to fill a gap with an inference, because a system that answers everything feels finished. But an answer you cannot trust costs more than no answer, since you have to go and check it anyway. Saying I cannot tell is a feature, and it is the one that made the rest of it believable.
From a photograph to a part on the bench
Turns a described problem into an object
Built with a command line tool that drives the printer over the local network, and parametric models written as code by Claude.
The bottleneck in home 3D printing was never the printer. It was that every idea required sitting down to model it, and the things I actually wanted were dull and specific. A mount for a clock. A guard for the corner of a beam that a kids' swing had worn bare. A cradle to hold a network switch under a desk.
So I built the path instead of the parts. I send a photograph and a description of the problem, Claude writes the model as code, renders it so I can see what I am agreeing to, slices it, and sends it to the printer. I check the bed through the printer's camera before it starts.
How it works
The model is code rather than a mesh, and that is what makes the loop work. Every dimension is a named value with a comment saying why it is that number. When a measurement comes back different from what I assumed, the fix is one line instead of a remodel, so a second version takes minutes. One guard went from a one inch upright to a two inch upright on the strength of a single tape measurement, and nothing else had to change.
Photographs do most of the work a drawing would. A picture of the worn corner, with a tape measure in frame, gives both the shape of the problem and the two numbers that matter. Most of my early failures were not modelling failures, they were measurement failures, and the questions worth asking are always about the thing the part has to fit rather than the part itself.
The unglamorous half is the printer. Slicing without the desktop software silently fell back to defaults, so a plate that needed 55 degrees was heating to 35, and a profile labelled fine was quietly printing coarse. The printer's own errors pointed at the wrong thing twice. A job that hung for nine minutes blamed the filament system, when the real cause was a calibration step this model has no hardware to perform. Both are written down now, which is the only reason the second attempt at anything is quick.
Two failures taught me more than the successes. A part with thin snap arms broke the first time it was fitted, because the plastic is stiff and cracks rather than bending. A screw recess modelled at exactly the size of the screw head came out too small to take it, because printed holes shrink slightly. Neither is visible in a render. Both are obvious the moment you hold the thing.
Example
A photograph of a beam where a swing had rubbed through the paint, plus two measurements, produced a rounded corner guard nine and a half inches long, with the screw holes countersunk and placed away from where the wear was. It printed that evening and went up with four screws.
What changed is not that I can make objects. It is that describing a problem carefully is now most of the work. The skill that still matters is measuring the right things and judging whether the result actually fits, which was always the part that was mine.
The arcade cabinet that runs the house
One surface for the whole system
An Arcade1Up cabinet with its original board removed, a Raspberry Pi behind the original screen, and new buttons.
Everything else here is a service. This is the place I actually stand in front of. I took an Arcade1Up cabinet, pulled out the board it shipped with, kept the screen, replaced the buttons, and put a Raspberry Pi behind it. It boots into a page called House Command, and it is the one surface where the whole system is in reach at once.
A phone can do all of this, and that is exactly why the cabinet earns its place. The phone requires you to decide to get it out, unlock it, find the app. The cabinet is already on, already showing the house, and already within arm's reach of the workbench.
How it works
The screen is a set of panels rather than a menu. Rooms, lights, scenes, media, and the 3D printer's camera and light, each one a tile you press. Pressing a tile calls into the house's automation system directly, so the cabinet holds no logic of its own. It is a control surface over the systems described above, which means anything I add elsewhere shows up here without the cabinet needing to know about it.
It changes what it shows depending on what is happening. Left alone it falls back to a clock. When a workout finishes downstairs, a debrief appears on the screen by itself, no button pressed, because the gym and the cabinet are the same room and that is the moment the summary is worth reading. The same debrief page also lands on the television in the gym.
And it still plays. The original cabinet's four games are on it, a button press away, which is the whole reason a cabinet is a better enclosure than a wall tablet. The house reports which mode it is in, so I can see from anywhere whether it is showing the dashboard or somebody is playing Galaga.
The Pi reports its own health back to the house rather than being watched from outside: temperature, load, uptime, and whether the chip has been throttled. That last one matters, because a Pi in a sealed wooden cabinet is a Pi in an oven, and slow throttling is the failure I would otherwise notice months late. The same rule as everywhere else applies. If the page stops serving, the house says so.
Example
I walk past on the way to the bench. The screen is showing the house at a glance. I press one tile to kill the lights upstairs, another to see the printer's camera without walking over to it. Twenty minutes after a workout, the debrief is sitting there waiting, and I did not ask for it.
The parts of this system were all built headless, which is right for building and wrong for living with. Giving it one physical surface changed how much of it I actually use. That the surface is an arcade cabinet is not a joke at the system's expense. It is the reason the thing is in a room I walk through, at a height I can use standing up, with buttons worth pressing.