Duval Kwali

I build backend systems where correctness and latency are both the point.

Four services, each one a problem I wanted to solve properly rather than demonstrate. They run on a single DigitalOcean droplet behind Caddy — deliberately small hardware, because constraints are what make the engineering legible.

Live now

LedgerGuard

Deployed

Fraud screening for financial transactions, with an audit trail you can prove nobody edited. Every decision is appended to a hash-chained ledger where each entry commits to its predecessor, so altering any historical row invalidates every hash after it and the tamper shows up on the next verification.

The interesting problem was never the rule engine. It was doing that append correctly and quickly with many requests in flight — the original implementation read the chain head and wrote the next entry inside the request, which let two concurrent screenings claim the same predecessor and fork the chain into something that could never verify again. Serialising every append through a single writer removed the race by construction and took the insert off the request path at the same time.

Measured on the same container, same load profile, same script

p95 latency
205ms

−71% from 717ms

p99 latency
314ms

−68% from 994ms

Throughput
230req/s

+99% from 116

Errors
0.00%

at 100 virtual users

0.75 CPU / 768 MB container, 100 VUs via k6. The "after" figure is the median of three warm runs — throughput here is bimodal against JIT warmup, and publishing whichever run came first would mislead in one direction or the other. Raw output and methodology are in the repository.

  • Java 21
  • Spring Boot 3
  • PostgreSQL
  • Redis
  • React 19
  • Docker
  • Prometheus

MIA.ai

Source available

A privacy-first WhatsApp business-messaging reply system. Llama 3.2 is self-hosted via Ollama, which holds per-message inference cost at zero against paid LLM APIs. Rule-based signal extraction handles roughly 70% of incoming messages, with the model as a fallback for the rest — so the expensive path only runs when the cheap one cannot answer. Redis caches prompt templates and conversation context, falling back to PostgreSQL on a miss.

  • TypeScript
  • Node.js
  • PostgreSQL
  • Prisma
  • Redis
  • Ollama
  • Next.js

In progress

Earlier work

Also: CS50x and CS50P (Harvard) — C, memory management, data structures, algorithms, SQL, and Flask across both courses. Coursework solutions are kept private per CS50's academic honesty policy.