The bot that runs the house
I shipped Phase 3 of a private Telegram bot — receipt capture, money logging, chores nudge, trip companion, strata reporter — then ran a high-effort v2 code review pass that caught eight real bugs. A build log about what it takes to make a command center you actually trust.
My site has an embarrassing secret: most of the interesting things it does are unreachable from the pocket. A money ledger, a chore board, a newsletter gate, a trip itinerary — each one is a private admin page that requires a browser, a login, and navigation. Telegram is the shorter path. I have been building it out one feature at a time, and Phase 3 just shipped.
One bot, a plugin router
The architecture has one entry point: a webhook. Every Telegram update arrives there, gets verified against an identity allowlist (unknown senders are silently dropped — no reply, not even a rejection; the bot should be invisible to anyone who guesses it), and dispatches to whichever feature module handles it.
Features register a slug, a push schedule, command handlers, and whether they need callback buttons. The bot token lives in the secrets store, rotatable from /launch/secrets with no redeploy. Feature toggles are database rows — the admin console writes a boolean and the next update sees it immediately. The callback buttons carry HMAC signatures so a stale button from a day-old message can't re-trigger an action.
What Phase 3 added
Five features in the household-action surface, all shipping in one PR:
Receipt capture — send a photo of a receipt and Claude's vision parse extracts merchant, amount, category, and purchase date. No command, no typing. The router dispatches photo messages separately from text, so the trigger is just: send a photo.
/money — type /money 12.50 Dunkin and a transaction is logged manually, category inferred from the vendor string. The whole interaction is one line.
Chores nudge — a morning push for overdue and due-today chores, with inline ✓ buttons. Tapping one calls completeChore() and re-renders the updated list in place. No app required to keep the board moving.
Trip companion — a morning push for the traveler's next stop, with a parallel 'home' view for the household watching the trip. One trip, two perspectives, one cron.
Strata reporter — the site's strata agent org finishes a run and the result arrives in chat: agent count, run date, surfaces affected. The loop closes without anyone opening a browser.
Each feature is around 70 lines. The message composers live in one shared, pure module — all unit-tested, none of them touching the network.
Eight bugs found on the v2 pass
After shipping I ran a high-effort code review: nine parallel finder angles — line-by-line diff scan, removed-behavior auditor, cross-file tracer, language-pitfall specialist, wrapper/proxy correctness, reuse/simplification/efficiency sweeps, altitude check. Eight confirmed bugs.
The most impactful: createTransaction() in the money-capture feature had no try/catch. A database error would propagate into the router as an unhandled exception, crash the update handler, and return a 500 to Telegram — which would cause Telegram to retry the webhook for 24 hours on every subsequent message.
The most subtle: the newsletter gate's Approve button called sendIssue(), and when that failed for reasons other than 'already sent,' the code returned a toast with the error message but left the gate message unchanged — buttons still live. The user could keep pressing Approve into a permanent failure with no visual feedback that anything had gone wrong. The fix is one line: call editMessageText to replace the buttons with the error before returning the toast.
Others in the batch: a \n prefix on the status URL line in health alerts producing a double blank line; the admin Test route missing runtime = 'nodejs' while the cron and webhook routes had it (the Edge runtime can't use the Node crypto APIs the HMAC signing needs); a save_failed receipt reply that didn't echo the parsed merchant and amount back; unexpected completeChore errors re-throwing into the router instead of returning a graceful toast; .heic and .gif missing from the MIME type map so iPhone Live Photos would be mistyped; and a .catch(() => {}) on a message-edit call that swallowed failures silently.
None were obvious at shipping time. The multi-angle pass is what surfaced them. The removed-behavior auditor finds dropped guards. The cross-file tracer finds call-site mismatches. The language-pitfall scanner finds the exception that propagates when nothing catches it. Looking for one kind of bug means missing three others.
What comes next
The site already has an Agent Store: a registry of autonomous tasks, each with a run queue, a result log, and a health canary. Strata already reports back to Telegram when it finishes. The next move is making the connection bidirectional — Telegram as both the notification channel and the trigger surface.
A /ask command backed by the concierge's semantic search. Agent Store tasks that push their completion to the bot. Tasks you kick off with a message. The household-as-personal-OS idea stops feeling speculative when the command surface is always in your pocket and the agents on the other side are already running. Pairing those two systems is the thing I am most excited about right now.
Get the next one
An occasional note when something genuinely new ships here — essays, free tools, projects. No schedule, no filler, easy out.
Need something like this built?
I design and ship AI tools, full-stack apps, and data pipelines — end to end, to production. Tell me the problem in a sentence; I'll give you an honest read on fit within a day.
Work with me →