A personal financial-intelligence agent that connects to any bank Plaid supports, classifies transactions, forecasts cash flow, detects anomalies and subscriptions, tracks a full envelope budget, and proactively pushes insights over Telegram — without being asked.
Share this case study →The hard part: turning messy real-world transaction data into numbers a family can trust every day, with no one checking its work.
ReAct orchestrator coordinating specialist agents through agent-as-tool calls
Python, SQLite schema v8, Plaid, Telegram Bot, APScheduler, Docker, Gitea Actions on NAS, M14 LLM gateway, A2A TraderJoe integration
Five specialist agents divide the work — a classifier, a budget advisor, an insight reporter, a Plaid-ops agent, and an anomaly sentinel — each backed by its own DeepSeek tool-schema functions: spending breakdowns, budget comparisons, net worth, cash-flow forecasting, anomaly detection, YNAB-style envelope budgeting, and more. A typical exchange: the user asks why spending is high; the agent calls get_spending and sees groceries doubled; it calls get_anomaly_alerts and sees a merchant's visit count jump from 3 to 8; it replies with the specific cause and offers to set a budget alert — reasoning across two tool calls, not pattern-matching the question. Beyond the chat, four scheduled jobs run without ever being asked: a daily budget check that only messages when something's actually over (silence otherwise — no noise for its own sake), a daily insight push combining the health score and anomaly detector into one Telegram message, a Monday-morning weekly report the agent writes about itself, and a twice-daily bank sync. The chat is one interface into the system; proactive alerting is the other. The long-term direction — not yet built — is connecting Family CFO with Trader Joe, so spending and investing are planned by the same system instead of two disconnected apps: one household, one picture, working toward the same goal of building wealth over generations. It's connected through Video Factory's shared LLM gateway with its own caller-identifying header, so usage is tracked separately from every other agent on that gateway.
Transaction classification is a three-layer cascade. Layer 1 uses Plaid's own structured categories (catches ~70%). Layer 2 applies keyword rules against known merchants (~20% more). Layer 3 falls back to an LLM for the remaining ~10% of ambiguous cases, returning a category, a confidence score, and a reason. When a user corrects a misclassification, the agent can add a new keyword rule on the spot — it learns in production without retraining. This is the fix behind a real bug: a naive mapping was once burying mortgage and auto-loan payments under a generic "transfers" bucket, distorting every budget calculation with no visible error.
Three rules, implemented in pure Python and SQL — no pandas, no scikit-learn. A transaction over 50% of its category budget is flagged high severity. A category exceeding its own historical mean by more than two standard deviations, computed from the household's actual data, is flagged medium. A new merchant charging twice within three months is flagged low, as a likely subscription. No model to train, no concept drift, and it correctly separates "genuinely abnormal" from "a little over budget."
Group non-deleted transactions by description, keep groups with two or more occurrences and a positive total, order by average amount. That one query surfaces every recurring charge in the account — Netflix, Spotify, gym memberships, insurance — without any pattern matching or external service. The agent doesn't need to know what a subscription looks like; the data pattern reveals itself.
Every Plaid transaction is normalized into an immutable record before it touches the database. Deduplication matches on date, description, and amount — checking both integer-cent and approximate-float representations to catch rounding differences — and a soft-delete guard ensures nothing the user removed is ever silently re-imported. The sync agent can run ten times in a row and produce exactly the same result as running it once, so a crash mid-sync is never dangerous.
Python, Plaid, LLM via the shared M14 gateway, APScheduler, Docker, SQLite.