Architecture Notes

Three machines and a laptop that sleeps

· 5 min read · Martin Kallenbach

For months, a good share of what went wrong had one cause: a job needed a machine that was asleep. How I settled what runs where, and why my code has one origin.

On the evening of 26 July and the morning after, Claude and I changed how my servers tell me something is wrong. The commit says why: “WhatsApp alerts depended on the Mac … bridge, … so they silently died whenever the laptop slept.” Since late June my home server’s critical alerts had run through a bridge program on my laptop, Falcon*; Claude and I had built them to use it. They arrived whenever I sat at my desk with the lid open, which is when I least need one.

Taking the laptop out of the paths that must work took until October.

One alert, two routes: through the laptop until 27 July, straight from the server since.
One alert, two routes: through the laptop until 27 July, straight from the server since.

A desktop that had to be awake

In March my system ran on a VPS in Helsinki, Bespin*, and my old Windows desktop, Dagobah*, which had the graphics card for a local model. ADR-001 (post 2) placed the journal routines on the VPS, yet they ran on the desktop’s scheduler until 17 May. The same record treats the desktop as a machine that “wakes” at 07:00 for the local-model work; when it was off, that work waited. From 22 May a new laptop was my main machine; my June notes call the desktop “powered off most of the time”.

A home server in four days

On 18 June I gave the desktop a second job. I wanted my photos off Google and a local language model, and for both its graphics card was the best I had. That day’s plan drew a line I still use: the VPS public and always on, the home box private and heavy. On 22 June, working through it with Claude, I wiped Windows; the machine came back as Yavin*, a Linux server with Docker and Immich for the photos. The next day the local model ran there, mail enrichment began to use it, and document processing moved over. Dagobah* and Yavin* are the same box: once mostly off, now always on.

Placed where nobody could reach it

On 24 June Claude and I built a finance-planning app with its engine on Yavin*. The next day my cloud sessions could not reach the server: the connector I deploy through pointed at an address inside my home network. That same day the app moved to Bespin*, and a decision note set the default: the VPS, with the home server only for what needs its storage or local AI. In August came the other half: whatever runs without me and needs no screen goes to Yavin*, so the laptop can sleep.

Where the work ran: March, May, June, and the scheduled jobs per machine on 10 October.
Where the work ran: March, May, June, and the scheduled jobs per machine on 10 October.

Curl-installed

On 26 June Claude checked the new server and found two scanning services watching one folder. An earlier build session had installed the second with curl (a command that downloads a file straight from the web) rather than cloning it from a repository. It raced the real one, uploaded to the cloud drive I was leaving, and talked to an empty second model server the same session had started by mistake. Three live services there were in no repository.

The rule that day: GitHub is the origin, and every machine running code holds a clone and only pulls. A daily drift check followed on 2 July; from 18 September a hook (a check before each command an AI session runs) blocked hand-written code on servers, at least in Claude Code.

The pattern

Put what must happen on what is always on; keep one origin for code, and let the servers only pull. On 7 July the weekly maintenance moved off the laptop, from Claude’s desktop app to timers on the servers. On 2 October, with the laptop asleep at three in the morning, a nightly secret scan ran only partly and a backup check reported a missing log. On 5 October the day’s focus reached my home page late, because it went through the laptop.

What I learnt: three questions place a job. Can my tools reach it? Does it need storage or a graphics card? Must it happen while I am away? Code needs one home too, or an AI session will build a second copy wherever it stands. Not solved: 13 scheduled jobs still run on the laptop, by my reading mostly looking after itself, and a few things I edit there wait for it to wake. More in post 11.

Part 4 of twelve in the series on how I built my AI OS. Start with the preface.

Next: a database built in a day, and the seventeen days it took to keep it fixed.

* Host names are changed (here: Falcon, my laptop; Bespin, my VPS; Dagobah, my old Windows desktop; and Yavin, the home server it became). The real ones come from a different saga.


Appendix — for builders

  • Take the laptop out of every path that must work (alerts, backups, maintenance), and test alerts by closing the lid.
  • Send alerts straight from the server. A messaging bot with an API took me about a hundred lines. Add a fallback; my watcher for long jobs uses mail.
  • Tell “asleep” from “broken”. My backup check read an unreachable laptop as a missing log; it now says “unreachable”.
  • One origin for code. Every runtime is a GitHub clone that only pulls, and nothing runs outside version control. Check for drift daily; later, block hand edits with a hook.
  • Point AI sessions at the real service. The phantom model server came from a command that did not say where the real one listened; one shell-profile setting ended it.
  • What I considered instead: everything on the VPS (always reachable, but no graphics card and too little room for photos); a storage box or small computer at home (the desktop’s card was better, and already there); one repository for all code (weighed on 26 June: a disruptive restructure of some 27 repositories for little gain). I chose the old desktop and one origin on GitHub because both reused what existed and closed the failures that had happened.

Also published on Kalli Family.