Engineering · Decisions
Architecture decision records.
Every technology in our stack got there through a decision we can defend. These are the records - context, decision, the why, and the tradeoffs we knowingly accepted. Each one lists what would make us revisit it, because a decision without an exit condition is a dogma.
ADR-001 · Flutter for mobile
Context: clients need Android and iOS; two native codebases double every feature. Decision: Flutter by default. Why: one codebase, both stores, near-native performance, fully custom UI - and one team owning the whole surface. Tradeoffs accepted: a Dart ecosystem smaller than JS; occasional platform-channel work for deep OS features. Revisit when: an app’s core depends on platform-exclusive capabilities - then we recommend native and say so in the proposal.
ADR-002 · FastAPI for Python services
Context: AI work lives in Python; APIs need typed contracts. Decision: FastAPI. Why: Pydantic validation at every boundary (half of API security for free), OpenAPI docs generated not written, async-first, fast enough. Tradeoffs: younger than Django - fewer batteries; we assemble auth and admin per product. Revisit when: a product is 90% CRUD admin - Django’s batteries can win there.
ADR-003 · PostgreSQL as the source of truth
Context: every product needs a database it will not outgrow in ways that hurt. Decision: PostgreSQL, always the default. Why: relational rigour, row-level security for tenancy, JSONB when shape flexibility is real, pgvector for AI retrieval - one engine, deeply known, covering 95% of data needs. Tradeoffs: not a warehouse, not a graph DB; extreme write volumes need care. Revisit when: analytics outgrow it (add a warehouse beside it - never replace it casually).
ADR-004 · Redis for the hot path
Context: caching, sessions, rate limits and queues all need sub-millisecond state. Decision: Redis for all four. Why: one operationally boring service instead of four exotic ones; data structures that match real needs. Tradeoffs: weaker queue semantics than a broker; memory is the budget. Revisit when: event streams need replay and fan-out - that is a broker’s job.
ADR-005 · Docker everywhere, Kubernetes when earned
Context: environments must match; scaling needs vary wildly by product. Decision: every workload ships in Docker; Kubernetes only when service count and scaling granularity justify it. Why: containers make laptop-equals-production true; Kubernetes is powerful and expensive to operate - premature adoption spends the innovation budget on plumbing. Tradeoffs: migrating a grown product onto K8s later is real work - we accept it, planned. Revisit when: many services, autoscaling needs, or multi-environment fleets arrive.
ADR-006 · Terraform for infrastructure
Context: four clouds (AWS, Azure, OCI, GCP), reproducible environments, reviewable changes. Decision: all infrastructure as Terraform. Why: one language across clouds, plan-before-apply, configuration that is documentation. Tradeoffs: state management discipline; occasional provider lag behind new cloud features. Revisit when: a team is all-in on one cloud with mature native IaC and no portability need.
ADR-007 · GitHub Actions for CI/CD
Context: code lives on GitHub; pipelines should live beside it. Decision: GitHub Actions runs the whole release pipeline. Why: zero extra infrastructure, reviewable YAML beside the code, marketplace depth, and our AI-review step drops in cleanly. Tradeoffs: vendor coupling; heavy monorepo builds need self-hosted runners eventually. Revisit when: build minutes or compliance demand self-hosted CI.
ADR-008 · Multi-provider AI (OpenAI + Claude + Gemini)
Context: model capability, price and policy shift quarterly. Decision: integrate all three behind one internal interface; route per use case. Why: reasoning depth, context length, latency and cost genuinely differ - and provider lock-in in 2026 is buying a single stock with the whole portfolio. Tradeoffs: an abstraction layer to maintain; evaluation suites must run per provider. Revisit when: a product’s needs collapse to one provider’s unique strength - then commit knowingly.
ADR-009 · Nginx at the gateway
Context: TLS, routing, rate limits and static delivery need a front door. Decision: Nginx. Why: two decades of production hardening, tiny footprint, does exactly what a product gateway needs. Tradeoffs: service-mesh features are not its job. Revisit when: the architecture genuinely becomes many-service with mesh-grade needs.
ADR-010 · Boring technology as policy
Context: every novel tool costs an innovation token; products have few to spend. Decision: spend novelty on the product, not the plumbing. Why: the stack above is deliberately unexciting - well-documented, hireable, operable at 3am. Clients get velocity and sleep. Tradeoffs: we occasionally adopt genuinely better tools a year late; we are content with that. Revisit when: a boring choice becomes a measured bottleneck - benchmarks first, migration second.
Related: The stack with reasons · Architecture gallery · Architecture library · Engineering journal