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.