Experiments & observations
The blog
Things I’m building, learning, and figuring out
26 posts
Real-time KYC card verification over MQTT: a pub-sub pipeline explained
A three-service KYC pipeline on MQTT: a client publishes ID card data, a verifier checks each field and publishes reasons, an analyst stores and charts results. Design choices and what QoS 1 implies.
Read the post : Real-time KYC card verification over MQTT: a pub-sub pipeline explainedHow to build a fully offline RAG chatbot with Ollama, Mistral, ChromaDB and Redis
A document Q&A chatbot for sensitive environments that needs no internet after setup: local embeddings and LLM through Ollama, ChromaDB for vectors, Redis caching, and the settings that matter.
Read the post : How to build a fully offline RAG chatbot with Ollama, Mistral, ChromaDB and RedisSecuring a voice AI agent's webhook: what my first version got right, and what I'd change
A 24/7 AI phone agent posts every call to a webhook. How HI Agent's backend verifies Vapi, stores repeated call events idempotently, and emails one summary per call, plus the fixes I'd make.
Read the post : Securing a voice AI agent's webhook: what my first version got right, and what I'd changeYour AI agent thinks its tasks were deleted: how to write tool errors for LLM agents
An agent saw 'task not found' and recreated its schedules, causing duplicates. Tool errors for LLMs must say why and what to do next. Lessons from NanoClaw and an MCP server.
Read the post : Your AI agent thinks its tasks were deleted: how to write tool errors for LLM agentsHow to see every AirPods battery on Linux, and why only one showed: LibrePods
LibrePods brings AirPods features to Linux and Android. With two AirPods connected, the Linux tray showed whichever updated last. A per-device registry fixes it, plus a smarter Android volume duck.
Read the post : How to see every AirPods battery on Linux, and why only one showed: LibrePodsFrom 10 µm to 3 nm: how chips got so small (and why 3 nm isn't 3 nm)
Intel's first microprocessor was built on a 10 µm process. Today's chips are sold as 3 nm. What shrinking transistors buys you, and what the node name really means.
Read the post : From 10 µm to 3 nm: how chips got so small (and why 3 nm isn't 3 nm)How I got my first pull request merged into LiteLLM
What my LiteLLM fix for context compression taught me about making a first open-source contribution to a large project: finding the issue, CI, and getting merged.
Read the post : How I got my first pull request merged into LiteLLMGoogle Workspace vs Zoho Mail for a small startup: is the price gap worth it?
For a 10-person team, Zoho Mail Lite cost about $120–150 a year against roughly $840 for Google Workspace. The trade-offs, and the DNS setup it took.
Read the post : Google Workspace vs Zoho Mail for a small startup: is the price gap worth it?What's inside a GPU? Cores, streaming multiprocessors and the memory problem
A GPU isn't one powerful processor. It's thousands of simple cores, specialised tensor and ray-tracing units, and a memory system built to keep them fed.
Read the post : What's inside a GPU? Cores, streaming multiprocessors and the memory problemHow can deployment platforms afford free tiers?
Servers didn't disappear. Platforms like Vercel and Render stopped renting you machines and started renting execution. Modern Deployments, part 1.
Read the post : How can deployment platforms afford free tiers?Why our AI agent looped forever: a context-compression bug in an LLM gateway
In LiteLLM, content an agent retrieved was compressed back into the same hash, so Claude Code never saw it and kept retrying. How the bug worked and how PR #38591 fixed it.
Read the post : Why our AI agent looped forever: a context-compression bug in an LLM gatewayWhy LLM cost dashboards lie when two deployments share a model name
LiteLLM's auto-router compared only provider and model, so two deployments of the same model at different prices reported $0 savings. Why identity should be the deployment.
Read the post : Why LLM cost dashboards lie when two deployments share a model nameLocalhost is not authentication: fixing a CWE-306 webhook in an AI agent framework
NanoClaw's loopback webhook accepted any local POST, letting other processes forge button clicks (CWE-306, CVSS 6.6). My fix, the maintainers' simpler one, and why theirs was better.
Read the post : Localhost is not authentication: fixing a CWE-306 webhook in an AI agent frameworkStreaming LLM output through a Next.js proxy: why you only get stream_end
Server-Sent Events through a Next.js route handler often arrive all at once or not at all. The headers, runtime flags and client parsing that make real-time LLM streaming work.
Read the post : Streaming LLM output through a Next.js proxy: why you only get stream_endWhy Node.js HTTP backpressure must count bytes, not string length
Node's OutgoingMessage counted buffered strings in UTF-16 code units, so multi-byte bodies were under-counted up to 3× and write() kept saying there was room. My fix, and how I verified it.
Read the post : Why Node.js HTTP backpressure must count bytes, not string lengthHow to build a secure MCP server with OAuth 2.1: lessons from a CRM connector
How I let Claude and other AI clients read a CRM safely: OAuth 2.1 with PKCE, single-use codes, row-level security as the only authorization, and read-only tools.
Read the post : How to build a secure MCP server with OAuth 2.1: lessons from a CRM connectorHow Rosetta, Wine and DXVK run software on machines it was never built for
Rosetta translates CPU instructions, Wine reimplements the Windows API, and DXVK turns DirectX into Vulkan. Three translation layers, three different problems.
Read the post : How Rosetta, Wine and DXVK run software on machines it was never built forWhat is a serverless cold start, really?
A cold start is the time a platform spends creating an execution environment before your handler runs. What happens step by step, and how to make it shorter.
Read the post : What is a serverless cold start, really?Your app doesn't run on a server anymore. It runs on a desired state.
Containers and orchestrators like Kubernetes mean you deploy a workload, not to a machine. What that changes, and why it matters. Modern Deployments, part 2.
Read the post : Your app doesn't run on a server anymore. It runs on a desired state.Labels vs annotations: how one YAML indent made Prometheus scrape Pyroscope twice
A Helm refactor moved prometheus.io/service-monitor from labels to annotations, so the ServiceMonitor stopped excluding Pyroscope's headless service and metrics were duplicated.
Read the post : Labels vs annotations: how one YAML indent made Prometheus scrape Pyroscope twiceHow to stop an LLM from inventing numbers: evidence containment and numeral tracing
In a research-to-LinkedIn pipeline, LLMs extract and write, but code decides what's true: verbatim evidence checks, discarding unsourced numbers, and tracing every numeral back to the paper.
Read the post : How to stop an LLM from inventing numbers: evidence containment and numeral tracingHow does Uber show cars moving in real time? A system design breakdown
Push instead of polling, H3 hexagonal indexing to find nearby drivers, and prediction plus correction to keep cars moving smoothly. How ride-hailing works at scale.
Read the post : How does Uber show cars moving in real time? A system design breakdownWhat actually gets deployed to production? (Not your code)
Production runs an immutable build artifact (a Docker image, bundle or JAR), not your repository. Why that makes rollbacks instant. Modern Deployments, part 4.
Read the post : What actually gets deployed to production? (Not your code)One phone number, two users: normalising WhatsApp sender IDs across Baileys and the Cloud API
Baileys sends 15551234567@s.whatsapp.net, the WhatsApp Cloud API sends 15551234567, so one person became two users with different roles. Canonical IDs and a merge migration fix it.
Read the post : One phone number, two users: normalising WhatsApp sender IDs across Baileys and the Cloud APIWhere do your environment variables go when you deploy?
Secrets pasted into Vercel or Render are not bundled into your build. They take a separate path and are injected at runtime, with one Next.js exception. Part 5.
Read the post : Where do your environment variables go when you deploy?How wide are London's pavements? Measuring 7,908 cross-sections in Whitechapel
No UK dataset records pavement width. I measured left pavement, road and right pavement for 10 Whitechapel streets, then tried four ways to prove myself wrong.
Read the post : How wide are London's pavements? Measuring 7,908 cross-sections in Whitechapel