Building Homunculus
One Electron window that trades crypto, watches the open web, and runs the rest of my personal ops.
For the last few months I've been building Homunculus — one Electron window that holds the things I check every day. Live markets, a real crypto portfolio, OSINT feeds, a budget, and an archive of everything the app has ever done. It started as a weekend dashboard. It's now the software I trust with the most sensitive things I own.
Homunculus has survived its own growth for one reason: every capability is a tab, and every tab is its own fault domain. I made that call in the first week and have been defending it ever since. A tab owns its data, its update loop, and its failure modes, and it reaches into nothing else — which is why a growing pile of features hasn't become a growing pile of coupling.
Why a desktop app at all
Trust boundaries, mostly. Homunculus reads a local budget, holds exchange API keys, and places real trades. I wanted none of it anywhere near a browser tab or somebody else's server. Electron gives you a clean split: secrets and privileged work live in the main process, the renderer draws the UI and never sees a raw key, and every privileged action crosses an IPC boundary I own and can audit.
// Secrets stay in the main process. The renderer never sees a key.
ipcMain.handle('gemini:placeOrder', async (_e, order) => {
const clientOrderId = idFor(order) // deterministic from intent
const existing = await reconcile(clientOrderId)
if (existing) return existing // a rerun cannot double place
return gemini.placeOrder({ ...order, clientOrderId })
})That boundary is also where the safety rules live. The renderer can ask to place an order. It can't place one itself. The main process decides, reconciles against whatever is already resting on the book, and stays idempotent, so a crash or a double click never turns into a duplicate order.
Electron solved reach, too. The same build runs on Windows, and Tailscale gets me to it from my phone or my watch when I'm away from the machine. Checking a live portfolio from my wrist is ridiculous, and I love it. One codebase, three screens, nothing leaving hardware I own.
One tab per capability
If you're building something like this, don't build a dashboard. Build a shell that hosts independent tabs. A dashboard shares one big state tree, so every new feature has to understand the last one. A shell of tabs doesn't. Each tab gets its own store, its own update loop, and its own definition of broken.
- CRYPTO — live market data, the real portfolio, signals, and an intel report.
- SURVEIL — OSINT watchers for the open web, including a Pentagon Pizza Index.
- FINANCE — a local budget that never leaves the PC.
- DATA and ARCHIVE — a history warehouse plus a running log of every action taken.
The payoff is blast radius. When the SURVEIL poller falls over — and pollers always fall over — CRYPTO never hears about it. Adding a capability means adding a tab, not threading references through code that already works. And debugging gets a fixed address, because a failure lives in exactly one place instead of somewhere in a shared graph.
The crypto tab
CRYPTO grew from a price viewer into a strategy engine that knows the portfolio, reads signals, and keeps a ledger of closed trades with real wins and losses. What surprised me is how little of the work is signal. Most of it is plumbing whose only job is keeping an automated system from hurting you.
Order placement is idempotent. Every order carries a deterministic client id derived from its intent, and on startup the engine reconciles resting orders against that id before it does anything else, so a crash mid-flight can't leave a duplicate on the book. A portfolio watcher manages positions that fall after entry, so a losing trade has a plan that doesn't depend on me being awake. And the accounting counts fees, because a scalp that looks green on raw price is frequently red once Gemini takes its cut.
I can leave it running and go to bed. That, not the signal, is what took months.
ARCHIVE is an event log
ARCHIVE is a scrolling wall of timestamps, and it's the tab that makes the rest of the app safe to run. Every meaningful action becomes an event that only gets appended, never edited. That buys two things a normal table wouldn't. I can answer what the system did and why at any point in the past, and I can replay the log to reconstruct state after a bug. Once you let software act on its own, an ordered record of its decisions stops being optional.
What's next
Homunculus is now the front end for a family of trading strategies I wrote as agent skills. That's the next post. Short version: I stopped writing trading bots as code and started writing documents that describe how to trade, then let an agent execute them against these same guardrails.
← Back to all posts