The old MacBook that never sleeps

25 October 2026

Most of my AI system works while my laptop is closed. It runs on an old MacBook Pro at home that never goes to sleep.

What the system does for me is in Every correction becomes a rule. This piece is about the machine underneath, and the times it stopped working without telling me.

I switched it off once

The first version was a background agent on an hourly schedule and a Telegram bot. On 10 June I turned both off. The agent had been exiting with an error since the day before, cause unknown. The bot was stuck in a timeout loop: the scheduler gave it 30 seconds, and a bot waiting for messages needs longer. It cost between $1 and $4 a day, and the upkeep was no longer worth it.

In April I had planned two old MacBooks: one as an always-on copy of my laptop, one for a separate assistant with its own Apple ID that I would text over iMessage. What exists today is one machine. In August the setup came back on it, with fewer runs and every failure written down.

What runs on it

They run as launchd jobs, the scheduler built into macOS. The notes arrive through iCloud. I reach the machine over Tailscale, a private network between my own devices, and open a terminal on it with SSH.

A scheduled job is a different user

August taught me this: a job started by the scheduler does not have what my terminal has, even on the same account.

On the night of 13 August I found out why mail and calendar had been failing in every cycle. The agent pointed at a version of Node that no longer existed on the machine, so every call to the Google tools died before it started. The next morning the briefing failed again, because Claude was not logged in on the server. I had assumed scheduled jobs could use the macOS keychain the way my terminal does. They can’t, and the error said so plainly: “User interaction is not allowed.” The fix was a long-lived login token in the job’s environment, with the job file readable only by its owner.

Two things made those failures look worse than they were. The start-up check confirmed that the Claude program existed, never that it was logged in. And skipped cycles wrote nothing to the log, so eleven quiet runs looked exactly like a dead agent. Both write a line now.

Two weeks later a half-finished refactor killed every run for five days. That story is in the other article.

Keys and permissions

Since 21 October the briefing agent, which reads my mail and chats, runs with an allowlist of read commands. It cannot send anything.

Hermes runs on its own Anthropic API key, paid per token. One setting stops it from borrowing or refreshing the Claude Code login the briefing depends on. If one of them hits a limit, the other keeps running.

Hermes, idle for two months

Hermes is an open-source agent from Nous Research with a built-in gateway to chat apps like Telegram and WhatsApp. When I checked on 20 October it had one session since 13 August and 1.5 KB of memory. Its code was about two months behind upstream, and it ran GLM through OpenRouter. My note that day: give it one job or retire it.

The first idea was a fallback for when Claude hits its limits. I dropped it, because the fallback would have sent my mail content to a third-party model. When Claude fails now, the briefing still arrives with calendar and tasks, built without AI.

The job it got is Bloom, a separate assistant with its own WhatsApp number. On 21 October I updated Hermes to v0.22.2. On 22 October Bloom got its own profile on the same gateway, with a Claude model, two tools (asking me a question and looking at an image) and no files, mail, shell or scheduler. It uses WhatsApp’s linked-device mode, which is unofficial. If WhatsApp bans the number, it bans the assistant’s, never mine.

Hermes has no hook that runs before a message goes out. I could patch the WhatsApp adapter, but the next hermes update would overwrite it. Bloom only replies to me for now. Before it sends anything on its own, the guard goes in: at least 45 seconds between messages, at most 30 recipients a day, no new chat without my yes.

Git instead of .bak7

Until 21 October the server scripts were not in git. Old versions were copies with names like .bak7 and .bak8, and the copies on my laptop and on the server drifted apart. Now the server folder is a repository. I edit a clone on my laptop and git push deploys. Secrets stay out, and the job files are committed with tokens removed.

Since 7 October my Claude Code sessions have a guard that checks risky commands before they run. It blocks the kinds that caused accidents before, like the sync that once overwrote the WhatsApp bridge’s data.

What I would tell myself in April

The hardware was the easy part. An old laptop I already owned, plugged in and set to never sleep.

The hard part was silence. My worst outages made no noise. They stopped producing output, and I read the quiet as a calm week. So the machine now checks itself every 30 minutes and messages me only when something is red. Every incident leaves a line behind.

An always-on machine has to tell you when it is off. What does yours do when it goes quiet?


I write What Actually Works every week: one real task handed to AI, with the prompts, the cost, and where it broke.