Skip to content

From the blog

Notes on software engineering, AI, and building products.

8 min read

Building reliable tools for LLMs

Function calling, agents, and MCP all rest on the same thing: tools an LLM can invoke. But a tool built for a non-deterministic, fallible caller needs different design than one built for code. A deep-dive on what makes a tool reliable for an LLM — naming, scope, validation, error messages it can recover from, and the safety that can't live in the model.

7 min read

The AI-native Rails stack in 2026

Put the pieces together — the no-Redis Solid stack, Kamal, pgvector, and the hard-won disciplines of building with LLMs — and a coherent picture emerges: Rails is an outstanding host for AI-native applications, precisely because it lets a small team build and run both the app and its AI features. A synthesis of where the stack stands.

7 min read

Designing human-in-the-loop workflows

The most reliable AI systems in production aren't fully autonomous — they keep a human at the decision points that matter. But 'human in the loop' is easy to do badly: rubber-stamp approvals, alert fatigue, humans blamed for the model's mistakes. A deep-dive on designing the human-AI division of labour so both do what they're good at.

7 min read

pgvector in production: lessons learned

I've championed pgvector as the sensible default for vector search. After running it in production, here's what the introductory tutorials leave out: index choice and its trade-offs, the recall-versus-speed dial, keeping embeddings in sync, and the operational realities that only show up at scale.

6 min read

Fine-tuning vs RAG vs prompting: choosing in 2025

There are three ways to make an LLM do what your application needs — prompting, retrieval, and fine-tuning — and teams routinely reach for the wrong one. They solve different problems, and the costs differ enormously. A trend post on what each is actually for, and a decision order that saves you from over-engineering.

7 min read

Self-hosting open LLMs: Llama and friends

Open-weight models like Llama 2 and Mistral mean you can now run a capable LLM on your own infrastructure instead of calling someone's API. A look at why you'd want to — privacy, cost, control — what it actually costs you in ops, and the honest question of whether you should.

7 min read

Function calling: letting the model use your tools

Function calling lets an LLM ask your code to run a function and use the result — turning a model that can only talk into one that can look things up and act. A deep-dive on how it actually works, why it's just structured output with a loop around it, and why you stay firmly in control of what actually runs.

5 min read

SOLID principles for a trading system

SOLID can feel like abstract acronym soup until a system grows complex enough to punish you for ignoring it. A war story applying the five principles to an algorithmic trading system — where each one earned its keep, and where dogmatic application would have hurt.

5 min read

Refactoring toward hexagonal Rails

Rails is a great delivery mechanism and a poor place to keep your business logic. A deep-dive on hexagonal architecture in Rails — ports and adapters, isolating the domain from the framework, and how far to take it before it becomes over-engineering.

4 min read

Ruby 2.2: incremental GC and symbol GC

Ruby 2.2.0 arrives on Christmas Day with two garbage-collector improvements that matter for real apps — incremental GC to cut pause times, and symbol GC to finally close a long-standing memory leak and denial-of-service vector.

6 min read

Running a Bitcoin node in Docker

A war story about packaging bitcoind in a container: the initial block download that humbles you, why the blockchain must live in a volume, locking down the RPC interface, and what a full node teaches you about long-running stateful services.