Skip to content

Why this stack

The active design is in ADRs 031+. This page is the two-sentence version of each choice.

Why Pydantic AI, not Agno / Hermes / from-scratch

Pydantic AI for tool-calling + dependency injection, with OpenRouter for provider-agnostic LLM access. No memory module, no auto-learning, no AgentOS UI — those were misfits for a system that has to be exactly reproducible per-transaction.

ADR-032 covers the comparison.

Why e-Financials, not Zoho / Xero / Merit Aktiva

e-Financials is the state-built Estonian bookkeeping platform (operated by RIK). It owns the official audit trail; the accountant logs in there. Building on top of it is the only way to deliver clean books to the accountant without parallel data-entry.

ADR-031 covers the comparison.

Why Discord, not Telegram / WhatsApp / a custom UI

Discord has a stable bot SDK, persistent message history, file uploads native, slash commands, button interactions, and private channels. Telegram came close (ADR-033 originally picked it) but Discord won on file-upload reliability and button-state persistence.

ADR-036 covers the pivot.

Why FalkorDB, not Memgraph / Neo4j

FalkorDB ships with the prod-falkordb shared container, Cypher-compatible, in-process Redis backend, no separate ops surface. We rejected Memgraph (heavier ops, more memory) and Neo4j (license + ops cost) for a single-tenant agent's tax-rule graph.

The KG is derivable from source PDFs so backup needs are minimal. ADR-039.

Why Enable Banking, not direct Wise API

PSD2 abstracts away per-bank quirks. One integration covers Wise + Revolut + most EU banks. Even when the operator's account is at Wise, going through Enable Banking gives a uniform PSD2-licensed surface — we don't need separate code for each bank.

Direct Wise integration was scaffolded then removed in commit 9045d0d.

Why LlamaParse, not Tesseract / Azure FormRecognizer / GPT-4 vision

LlamaParse handles structured field extraction (vendor_name, invoice_date, gross_amount, vat_amount) better than raw OCR because it's tuned for documents. It's cheaper than GPT-4 vision at our volume.

Quality is "good enough" — when it surfaces wrong fields, the operator catches them in the HITL prompt before booking.

Why Turso, not Postgres

The agent's relational data fits in <1GB and has no shared- multi-tenant requirements. Turso (libsql) gives us SQLite semantics with a hosted, replicated backend. Local dev uses a file; prod uses a libsql URL. Same code path.

Postgres would be overkill and add an ops surface.

Why Zensical for docs

Markdown content, sidebar navigation, built-in search, and a static bundle deployable to Cloudflare Pages. Cheap to build and own.

Why Cloudflare for hosting

Pages handles the static site + Access provides the gate (free tier 50 users). One vendor for DNS + CDN + static hosting + auth. Already used elsewhere in this VPS (Tunnel + Workers).

Why one image, three CMDs

Already covered in The three deploys. One image push → all three containers update in lockstep. No version skew possible across the bot, webhooks, and booker.