I Gave My Family a helper bot. It Gave Me a Post-Mortem.

What twenty-one launchd plists, a spreadsheet pretending to be a database, and a God Object called imessage_bot.py taught me about the systems I sell for a living.

Somewhere between the third school-portal scraper and the moment my daughter texted a Mac mini to ask about her hockey fixtures, I stopped thinking of GROMY-SERVER as a weekend project and started thinking of it as a system. That's usually the point where things go wrong — not because the idea was bad, but because nobody stopped to ask what the idea would look like at scale. I've sat across the table from enough CIOs to know how that story ends. I just didn't expect to write the post-mortem on myself.

How It Started

GROMY-SERVER began as a sensible answer to a boring problem: too many family calendars, too many school portals, too many things my wife and I were tracking by memory. Over a handful of build sessions it grew into something genuinely useful — a persistent macOS host quietly managing our shared calendar, my daughter's Year 7 timetable, the Weybridge-to-Waterloo commute, a Tesco price watch, and a NAS health check, all stitched together with launchd timers and a family iMessage bot you could just... talk to.

It worked. That was the problem. Nothing forces you to confront your architecture like something that works well enough to keep bolting things onto.

The Anatomy of an Accident

By the time I sat down to review it properly, the stack looked like this: 21 uncoordinated launchd plists firing independently, three or four "monolithic script engines" doing the actual work, and all of them converging on the same shared disk state — an 18-column Excel workbook on OneDrive that had quietly become a human UI, an operational database, and a message bus, all at once.

I've stood in enough vendor pitches to know the term for that: it's the architecture equivalent of a client telling me their CRM, their data warehouse, and their alerting system are all "the same spreadsheet, basically." I've sold against that pattern. I had just also built it, in my own kitchen, for my own family.

The three anti-patterns I'd have flagged in someone else's stack

  1. The shared spreadsheet as a concurrent database. Excel has no ACID guarantees and no row-level locking, so scripts writing to it needed artificial retry loops — three attempts, fifteen-second delays — just to survive OneDrive's file-lock collisions. A spreadsheet is a UI wearing a database costume.
  2. High coupling to the local GUI. The iMessage bot needed Full Disk Access and AppleScript to function. The calendar sync leaned on Calendar.app because a consumer Microsoft 365 account has no direct Graph API access. The school pipeline routed through Apple Mail and a physical printer driver. Any one macOS dialog box, permissions reset, or WindowServer hiccup could take the whole operation down — the home-server version of a single point of failure hiding behind a shiny UI.
  3. Leaky abstractions and half-finished queues. At one point I stripped direct notification calls out of the school-calendar script to "enforce separation of concerns" — good instinct — but never finished building the message queue it was supposed to hand off to. Result: school reminders quietly vanished into a queue that didn't exist. I'd built half a decoupling and called it done, which is worse than not starting.

Meet the God Object

If the report has a villain, it's imessage_bot.py. What started as "a script that replies to texts" ended up as a database poller, an NLP-ish intent router, a multi-step conversation state machine, a domain query engine, a text formatter, and an AppleScript transport client — all in one execution path. It touched Excel directly, called train APIs, managed cancellation states, and stripped markdown asterisks so iMessage wouldn't render literal stars. Any failure in any one of those jobs — a flaky train API, a locked spreadsheet — could crash the entire family messaging interface.

I have described this exact failure mode to enterprise clients using different nouns. It's always the same shape: one component absorbs just one more responsibility because it's convenient, and eventually it's load-bearing for everything and understood by no one, including, on a bad day, the person who wrote it.

What I'm Actually Doing About It

The fix isn't cleverer scripts. It's the boring architecture lesson I'd give any client: separate collection from decision from delivery, and don't let any one layer know the others exist.

The rebuild — running alongside the original, cut over only when it's proven itself — splits everything into three strict layers. Collectors write state and nothing else; they never touch Calendar or send a message. An orchestrator reads that state, routes commands, and queues outbound messages. A delivery layer drains the queue and is the only thing allowed to actually send anything. The iMessage bot becomes a thin router with a plugin folder per command, so adding "what's for dinner" as a query never means touching the code that handles the school timetable. Excel gets demoted from system of record to export format. State moves to SQLite. Twenty-one plists become one orchestrator.

None of this is exotic. It's the same producer-queue-consumer pattern I've pitched in board rooms under a different name. It turns out the honest test of whether you believe your own advice is whether you'll apply it to something as low-stakes as your daughter's hockey fixtures.

The Bit Worth Keeping

I'm not writing this to flagellate myself over a home project — the first version worked, my family used it daily, and "worked, but ugly" beats "elegant, but theoretical" every time. But there's a reason I keep a folder of Nokia post-mortems on this blog: the value was never in getting it right the first time. It was in being willing to write the autopsy on my own system with the same discipline I'd apply to someone else's, and then actually rebuild it instead of just diagnosing it and moving on.

If you're the person in your organisation quietly running the equivalent of GROMY-SERVER — the spreadsheet that's become a database, the one script that does everything, the queue that was half-built and forgotten — you already know the ending. The only real question is whether you write the post-mortem before or after it breaks in front of someone important.