The morning email is easy to take for granted. It arrives, tells me what needs attention, and disappears into the day. Moving Bubbles to different software exposed the strange fact underneath it: the email was part of our routine, but its schedule belonged to a replaceable piece of software.

The conversation moved in one afternoon. The quiet promises attached to it did not. Schedules, identities, tool connections, and machine-specific configuration had accumulated around the old runtime until changing the brain became the smallest part of the move.

I am writing this on migration day, while I can still remember which absences surprised me.

The runtime was the small part of the migration

These items all needed new connections. None of them is “the agent”:

  • Each scheduled job. The jobs lived inside the runtime’s own scheduler.
  • Each tool server: home automation, media, and the network gear. Each one has its own launch command and config format.
  • The channel identity that the agent speaks through.
  • The persona and behavior config.
  • Its skills.
  • A pile of helper scripts that the agent calls.
  • The file-sync rules between my laptop and the machine that runs the agent.

That is seven categories. Only one of them is the model. Everyone benchmarks the runtime. The runtime is also the component that you will replace first. Each other item on the list lives longer than the runtime. Each item stored inside the runtime is an item that you must redo at migration time. The count of parts bolted to a runtime is directly correlated with how permanent the runtime felt on install day.

I now ask one question before I add a capability to an agent: if I replace the runtime tomorrow, does this capability come with me? If the answer is no, the capability is in the wrong place.

The scheduled jobs expired every seven days

This discovery made me stop and stare.

The old runtime managed its own scheduled jobs: a morning brief, a weekly music roundup, a Friday movie pick, and a media-queue cleanup. The runtime also expired those jobs after seven days. That was the design.

A mitigation was already in place when I inherited my own setup: a nightly restart at 4 AM created the jobs again.

Sit with that for a second. The scheduler deleted its own jobs every week. A restart loop covered the hole. The arrangement worked. It worked for months. And the arrangement was truly bad. The problem was not the seven days. The problem was the failure mode.

An expired job causes no error. There is no exception, no alert, and no red light. The job is simply gone. A job that does not exist cannot fail. It is only absent.

If the nightly restart missed one night, the first symptom would be a missing email at 6:33 AM. Nobody notices a missing email. You notice a wrong email. You do not notice the absence of a thing that you stopped expecting. A daily automation becomes that thing after about one week.

So the arrangement had two silent layers, one on top of the other. The scheduler forgot its jobs quietly. The watchdog also failed quietly. One unmonitored restart script stood between me and the loss of my morning brief.

Durable items belong outside the component that you will replace

The principle that I take from today: do not keep schedules and state inside the agent runtime.

The reason is not that this runtime is bad at scheduling. The reason is that the runtime is the most volatile component in the whole stack. It has the fastest release cadence. Several companies reinvent it at the same time. And it is the piece that I replaced today, which proves the point. A durable item must live in infrastructure that is boring enough to exist in one year.

A schedule is a fact about my life. I want a brief at 6:33. I want a movie pick on Friday. That fact has no connection to the fashionable agent framework of the quarter. If you encode the fact inside the framework, you must encode it again at each framework change. The schedule also inherits the failure modes of the framework. A seven-day expiry is one of those modes, and nobody selects it on purpose.

I want this version: a durable scheduler in a dumb, reliable place that calls the agent. The agent becomes a stateless thing that you invoke. Then you can replace the agent on a Tuesday and touch zero schedules.

I did not build that yet. Today was the migration. The schedules moved in their old shape, because a rebuild was a second project. But now I know what the second project is. This morning I did not.

Two smaller lessons about identity

Give the agent its own account. I gave the assistant a dedicated Apple ID for messages. I did not share my own. This is not fussiness. A shared account makes the messaging system merge the two identities. Then separate conversation threads become impossible. The agent’s messages and your messages become one stream, and you cannot tell them apart.

The general rule is worth a plain statement. An agent that acts in your accounts must have its own identity where the platform permits one. You get threads. You get attribution when you read back through the history. You get a revocation plan that is not “change my own password.” A shared identity with an autonomous agent is a free shortcut until the first thing goes wrong.

Sync the portable config. Exclude the machine-specific config. My laptop and the Mac Mini sync some config between them. Their home directories have different usernames. A config file with an absolute path is correct on one machine and silently wrong on the other. There is no error. The config only points at a directory that does not exist. This made more than eighty sync-conflict files before I looked.

The fix was easy: an ignore file on both machines that excludes the machine-specific paths. The hard part was to know where to look. The signal was small. Nothing crashed. Some plugin configs only behaved differently on each machine.

What I will do differently

If I built this from zero instead of a migration, I would try this line first: the runtime gets the conversation loop and the tool calls, and almost nothing else. Schedules, persistent state, and identity live outside it. Then the runtime becomes the component that I can throw away. A replacement must cost an afternoon, not a day. The runtime is a rental car. Keep nothing in the trunk.

I write this on migration day, so I will admit the obvious point. This is not the last runtime for this assistant. The field moves too fast for a settled answer. For now, the useful design question is not which runtime. The question is what I can keep outside whichever runtime I try next.