← Selected work

Steward

A personal financial AI planner: your full money picture reconstructed locally, plans simulated nightly, with a hard rule that the data never leaves the machine.

Stack: Python · SQLite · browser automation · static HTML + cron Status: Private, running daily Links: none public
Screenshot of the live dashboard with real data — the daily self-hosted build.
Screenshot of the live dashboard with real data — the daily self-hosted build.

01The problem

Your financial life is most complete inside your brokerage's web portal — and least portable anywhere else. The cloud aggregators that promise a unified view want, as the price of admission, your credentials and a continuous feed of your financial history routed through their servers. I've spent a career building the systems that other people's sensitive data flows through; I know exactly what "we take security seriously" is worth when the business model is the data. What I actually wanted was modest: the portal's picture — positions, activity, cash flow, tax context — queryable on my own hardware, with no third party in the loop.

02The approach

Steward is a pipeline, not an app. A read-only browser automation pulls the brokerage's own tables — the positions page is an ag-Grid widget whose pinned label column and centered data cells arrive as separate fragments, so the parser pairs them row by row, stripping section headers and totals — and lands everything in a local SQLite database: accounts, positions, transactions, daily snapshots. From that store, a deterministic analysis pipeline computes subscription-like recurring charges, cash-flow trends, concentration risk, and opportunity scans, and a static HTML dashboard is regenerated daily by cron. No server, no daemon with a port, no login. The dashboard is a file; the database is a file; the whole system is a directory I can back up.

03Trade-offs

Scraping over aggregators — browser automation of a portal is fragile in a way an API never is: a redesign breaks the parser. I accepted that fragility because it sits on my side of the trust boundary. A broken scrape shows up as a failed cron run I fix in an hour; a breached aggregator is someone else's incident with my data in the headline. The parsers are deliberately dumb and explicit — regex over pinned-row labels, known account-name mappings — so they fail loudly rather than silently mis-parsing.
Account-aware tax logic — a rebalancing suggestion that ignores account type is noise. The engine classifies every account — taxable, Roth IRA, traditional IRA, ESPP-retirement — and treats them differently: trades inside tax-deferred accounts are not taxable events, so opportunity scans run only against taxable holdings, and loss-harvesting logic never fires where it can't help. The same dollar means three different things depending on which wrapper holds it; the schema knows that even when generic tools don't.
Local-only inference, enforced — the analytical core is deterministic rules; where a language model narrates or explores the data, it is pinned to a local inference engine. This started as a prompt-level intention, and a misconfiguration once routed inference to a cloud model anyway. It was caught, fixed, and promoted into the AgentOS policy layer as a typed rule: financial target-class data may never reach a cloud-locality model — local only. The lesson is that a privacy promise resting on a prompt is a wish; the same promise as a schema-checked policy with a named enforcer is a boundary.

04Under the hood

The repo is a small monorepo of plain scripts: an ingest/ layer (position parser, activity parsers for two export formats, a local token-handling helper whose callback server binds to 127.0.0.1 only), an analysis/ layer (subscriptions, cashflow, opportunities, insights — each writing structured JSON), and a dashboard/ layer that exports a JSON snapshot into an HTML template. A single pipeline runner executes the steps in order and stops on the first failure, so a bad day is visible rather than half-rendered. Cron drives the daily refresh and the dashboard rebuild; the AgentOS ontology tracks the job, its state files, and its failure routing like any other member of the fleet. You will not find balances, account numbers, or dollar figures on this page — by design, that part of the system stays where it belongs.

← All workNext →