All projects SIDE PROJECT · TYPESCRIPT

Personal Agent — Family OS

A family operating system, not just a chatbot. Message it on Telegram, WhatsApp, or email and it extracts events and tasks with an LLM; a companion admin web app handles everything chat can't — multi-user roles, medical records, and an EOS-style Rocks/Scorecard/Issues tracker.

TypeScriptCloud RunFirestoreHexagonal ArchitectureOpenRouterExpress

FROM EXTRACTION BOT TO FAMILY OS

Started small. Didn't stay that way.

Personal Agent began as a single-channel proof of concept: send it a message, it extracts an event or task with an LLM and creates it in Google Calendar or Trello. That core still works exactly the same way, across email, Telegram, and WhatsApp, including photos and voice notes sent directly to the model as multimodal input.

What's grown around that core is a second, more conventional application: a session-authenticated admin web app for things chat was never going to handle well — multi-user accounts and roles, per-person calendars and medication reminders, family medical records, a baby exclusion-diet diary, and an EOS-inspired Rocks/Scorecard/Issues tracker for running the household like a small team.

Built solo, end to end — domain logic, every channel adapter, and the admin app.

3inbound channels: email, Telegram, WhatsApp
4scheduled digests + standing watch
0vendor SDKs inside the core use case
Still growing,still in daily use.

PORTS & ADAPTERS

One use case, every channel.

processInboundMessage is the only place business logic lives — it has no imports of Google, Trello, or OpenRouter SDKs. Every channel and every integration is an adapter implementing one of its port interfaces.

Personal Agent architecture: Telegram, WhatsApp and email feed processInboundMessage, which calls LLM extraction, Calendar, and Trello adapters and reads/writes Firestore for dedup and memory; the browser-based admin app talks to the same Firestore through its own routes. CHANNELS PROCESSING ADAPTERS EXTERNAL DATA Telegram bot webhook WhatsApp Cloud API webhook Email IMAP poll · 15 min Browser admin UI processInboundMessage channel-agnostic core use case dedup + thread-aware memory admin routes Express · session auth LLM extraction OpenRouter · Gemini 2.5 Calendar adapter multi-calendar Trello adapter cards + member assignment Google Calendar per-person events Trello task boards Firestore
Channel / adapter
Core logic (Cloud Run)
Managed datastore
External system

Not shown: Cloud Scheduler triggers daily, midday, and close-of-day digests, a weekly digest, and a standing watch for overdue tasks — all reusing the same channel adapters to send proactive messages back out.

WHAT EACH PIECE DOES

One core, six adapters.

01

Core use case

processInboundMessage and its port interfaces hold all business logic and have zero imports of concrete infrastructure — every entrypoint builds a Ports object and passes it in.

HexagonalTypeScript
02

Channel adapters

Telegram and WhatsApp are driving + driven adapters (they reply in-thread); email is driving-only, since replying was never built.

WebhooksIMAP
03

Extraction

An OpenRouter adapter (Gemini 2.5 Flash) classifies tasks into categories and extracts structured events/tasks from text, photos, and voice notes — no separate transcription step.

OpenRouterMultimodal
04

Memory & dedup

Firestore-backed, responsible-aware duplicate detection, thread-aware follow-ups (a reply confirming a date updates the existing event), and short-term conversational context.

Firestore
05

Admin web app

A conventional session-authenticated Express app: multi-user roles, family profiles with per-person Google OAuth and medication reminders, medical records, and a message log with rollback.

ExpressSessions
06

Family operations

An EOS-inspired Rocks/Scorecard/Issues tracker, source and exception rules, and a standing watch for overdue tasks — running the household with the same discipline as a small team.

Cloud Scheduler

WHY IT'S BUILT THIS WAY

A few deliberate trade-offs.

  • The core use case never imports a vendor SDK directly — every integration goes through a port, so swapping Trello for something else later touches one adapter, not the business logic.
  • Dedup is responsible-aware on purpose: the same event mentioned by two different family members in two different channels still resolves to one Calendar entry.
  • Cleanup scripts delete the Firestore dedup record along with the external artifact — deleting only the Calendar event leaves a stale memory entry that silently skips a real future request as a "duplicate."
  • The admin app is a deliberately separate, conventional Express app rather than forced into the hexagonal core — not everything needs to be channel-agnostic.