Meet Bubbles: The AI Assistant I Text Like a Friend
Bubbles runs on Hermes, lives on a Mac Mini, and has an iMessage number through Photon. The useful part isn't the chat—it's the skills that connect it to the tools I already use.
What broke, what I built, and what I’m still learning.
Newest first. The useful failures tend to age well.
Bubbles runs on Hermes, lives on a Mac Mini, and has an iMessage number through Photon. The useful part isn't the chat—it's the skills that connect it to the tools I already use.
I gave our family AI access to twenty years of photos. The interesting engineering wasn't what I let it do — it was everything I refused to let it do, and keeping the captioning local so the photos never leave the house.
Every travel tool assumes you already know where you're going. Ours starts at 'someday, Tokyo' and tracks it through to a day-by-day plan — plus the bug where marking a trip booked got un-booked forty-five seconds later.
Taste used to live in my source code, so changing what I like meant editing TypeScript and redeploying. Now it lives in a note in Obsidian, and every app that cares reads it at runtime. The publishing mechanism is 24 lines.
Fourteen data sources, two LLM calls, one email, zero machines I have to keep awake. Every fact is rendered in TypeScript and only the voice is generated — plus the cron bug that sent a Friday email on Thursday for weeks.
My want-to-watch list was sharded across three services, none of which knew what I already owned. So the movie picker kept recommending films sitting on my own server. Building the state layer was easy; making it stop suggesting the same movie every night was not.
I tried to get my family AI legally approved to send text messages. Twilio's compliance robot rejected the same field four times, twice after I pasted in its own suggested wording. The robot won.
The API console showed a key burning $175/month — a key that shouldn't have been in use at all. It traced back to a background 'dream' loop I'd accidentally installed, whose built-in budget guard was under-counting its own spending by 7x.
The family AI now reads everything I know: the kids' summer schedules, project decisions, business questions, years of notes. A searchable brain over my whole vault made it possible — plus two fixes that made it dependable. I love this thing.
The family AI went silent and everything pointed at an API outage. The actual cause: a debug command I'd forgotten about, hung for 16 days, eating all 16,384 network ports.
Capture was never the friction in a task app; filing is. So I self-hosted one that files for me, then discovered the more interesting use: my machine agents file tickets in it too, claim them race-safely, and hand work back.
I texted my AI a screenshot and it swore no image arrived. The iMessage adapter was compiled bytecode with no source — it had never handled inbound images at all. So I taught it to, without ever seeing its code.
My AI's knowledge base survived its rebuild for 28 hours. Then macOS sent a kill signal mid-checkpoint and corrupted the whole database — for the second time in 24 hours. A forensic timeline, two zombie processes, and an honest ending.
463 session transcripts were sitting on my disk as unsearchable JSONL. Now every session that matters gets redacted, graded, summarized by a cheap model, and filed in my notes automatically — 315 notes so far, for about three dollars a month.
At 7:48 one morning, Bubbles blew up my phone with texts nobody asked for. The investigation took six and a half hours, ended one entire agent runtime, and produced a rule I still live by: the assistant doesn't get to text me.
The file was indexed. I verified it. And a two-word query still couldn't surface it. A better embedding model wouldn't have helped — what worked was a second retriever with different failure modes, fused by rank instead of by score.
Swapping the agent runtime took an afternoon. Re-pointing everything bolted onto the old one took the rest of the day — including scheduled jobs that expired every seven days behind a nightly restart nobody was watching.
Traffic from ten domains I never checked became one plain-language morning email. It showed me that 196 server requests represented only three actual visitors — then ran stale code for two days without an error.