What we learned shipping AI agents to production
Agents are fun in a demo and humbling in production. Here are the guardrails that made ours reliable enough to trust.
Notes on software engineering, AI, and building products.
Agents are fun in a demo and humbling in production. Here are the guardrails that made ours reliable enough to trust.
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.
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.
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.
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.
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.
Rails 8 is coming with a quietly radical idea: you might not need Redis anymore. Solid Queue and Solid Cache move background jobs and caching into your database, collapsing the standard Rails production stack. A look at what the Solid libraries do, why 'just use Postgres' keeps winning, and when the old answer still applies.
Once your semantic search outgrows a toy dataset, the question arrives: stay with pgvector in Postgres, or move to a dedicated vector database like Pinecone, Qdrant, or Weaviate? A deep-dive on what actually changes at scale, what you give up by leaving Postgres, and how to decide without over-engineering.
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.
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.
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.
Kubernetes is the industry-standard way to run containers — and a large operational commitment. An honest look at what it gives you, what it costs, and whether a small team building a Rails app actually needs it (usually: not yet).
A war story: we built fast on MongoDB, hit the limits of the document model, ran Mongo and Postgres side by side in one Rails app, and migrated collection by collection to Postgres without downtime. What it cost, how we did it, and what we'd do differently.
A catalog page that's fast for ten users can fall over for ten thousand. A deep-dive on the layers of caching that keep it fast — fragment caching, HTTP/CDN caching, low-level caching — and the hard part: invalidation and stampedes.
Selling across countries means translating not just your interface but your data, your URLs, your formats, and your SEO. A deep-dive on Rails i18n at scale — locale files, translated content, locale routing, and the traps.
A product catalog is mostly images, and naive image handling will sink it. A deep-dive on building an image pipeline that scales — keeping originals, generating variants in the background, offloading to a CDN, and not melting your web servers.
Selling in more than one currency sounds like a display problem and turns out to be a data-modelling one. A deep-dive on representing money correctly, storing prices across currencies, exchange rates, rounding, and the mistakes that cost real money.
Rails 5 has reached its release candidate. A tour of what's new — first-class API-only apps, ActionCable, ApplicationRecord, the attributes API, required belongs_to, and the rails command swallowing rake.
Rails 5 brings ActionCable — WebSockets integrated into the framework. A deep-dive on channels, subscriptions, and broadcasting, how the Redis-backed pub/sub works, and the scaling realities of holding thousands of open connections.
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.
Ruby 2.3.0 lands on Christmas Day with two changes you will type every day — the frozen-string-literal pragma and the &. safe-navigation operator — plus dig, did_you_mean, and more. A practical tour.
Long-lived feature branches cause painful merges and big-bang releases. Feature flags let you merge to trunk continuously and decouple deploy from release. A guide to the workflow, the kinds of flags, and managing flag debt.
The N+1 query is the most common performance bug in Rails apps, and it hides perfectly in development. A deep-dive on what causes it, the difference between preload/eager_load/includes, counter caches, and how to catch it automatically.
Every Rails request passes through a stack of Rack middleware before it ever reaches your controller. A deep-dive on what Rack is, how the middleware stack works, and how to write your own for logging, auth, and more.
SASS gives you power; architecture is what stops you abusing it. A deep-dive on structuring stylesheets so they scale — design tokens, the 7-1 folder pattern, BEM, managing specificity, and thinking in components instead of pages.
SQL LIKE is not search. A deep-dive on adding real full-text search to a Rails app with Elasticsearch — analyzers, mappings, relevance, keeping the index in sync — and an honest take on when Postgres is enough instead.
Capistrano automates deployment over SSH with atomic releases and one-command rollbacks. A practical guide to the releases/current model, shared files, the Rails task chain, multistage config, and the gotchas worth knowing.
Skinny controllers pushed all the logic into fat models, and now they're unmanageable. Service objects are the most popular fix — here is how to use them well, when they help, and how they turn into a worse mess if you overdo it.
Sidekiq processes background jobs with threads and Redis, and it is fast — but the speed comes with rules. A deep-dive on how it works, why your jobs must be small and idempotent, and how to survive retries and concurrency.
Postgres 9.4 brings JSONB — indexed, queryable JSON inside a relational column. A deep-dive on when to reach for it, how to query and index it, the Rails 4.2 integration, and the trap of treating your SQL database like a document store.
A war story about standing up the infrastructure behind a cryptocurrency project — coin daemons, wallets, and key handling — reproducibly, with shell scripts and Ansible. The security lessons that only a money-handling system teaches.
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.
The Rails 4.2 release candidate is out. A look at the three changes that matter most — a unified background-job API, a quietly large performance gain in ActiveRecord, and foreign keys finally landing in migrations.
Why a static site is the right tool for a developer blog, and how to build one with Jekyll and host it free on GitHub Pages — front matter, Liquid layouts, Markdown posts, and writing in the same workflow you already use for code.
AngularJS makes the DOM react to your data automatically. A practical introduction to two-way binding, directives, controllers, and dependency injection — plus an honest look at the digest cycle and where the magic costs you.
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.
Ansible configures servers over plain SSH with no agent to install. A practical guide to provisioning a Ruby app server with idempotent playbooks and reusable roles — and why agentless won us over.
Clicking through the AWS console does not scale past a handful of servers. How we drive an EC2 fleet from Ruby — declaring instances as data, reconciling desired against actual, and tagging your way to sanity.
Rails 4.1 is a quietly excellent point release. A tour of the features we adopted immediately — config/secrets.yml, ActionMailer previews, ActiveRecord enums, the Spring preloader, and request variants — and the small print on each.
Docker is approaching 1.0 and it is about to change how we ship Ruby apps. A hands-on guide to images, containers, and Dockerfiles — building a reproducible dev environment and why 'works on my machine' is finally on its way out.
Cucumber is either a bridge to your domain experts or an expensive layer of indirection — and which one depends entirely on how you write it. Here is where it earns its keep, where it doesn't, and the rules we follow.
Users misspell things, and exact matching fails them silently. A practical deep-dive into trigram-based fuzzy matching in Ruby — how it works, how to make it fast, and when to push it into Postgres instead.
Two tools we reach for on every Rails project. How HAML makes templates readable and SASS keeps stylesheets maintainable — with the patterns that work and the traps (over-nesting, @extend) that don't.
Ruby 2.1.0 shipped on Christmas Day, as tradition demands. The headline is a generational garbage collector, but the language changes — required keyword args, refinements, Exception#cause — are what will change how we write code.
Your test suite runs on one core while the other three sit idle. A thorough guide to splitting RSpec across processes with parallel_tests — separate databases, runtime-balanced groups, the isolation bugs it surfaces, and scaling it out across CI nodes.
A weekend project: a tiny command-line password vault built on Ruby and OpenSSL. A thorough tour of key derivation, IVs, authenticated encryption, constant-time comparison — and every way we found to get crypto silently, confidently wrong.
We have been shipping MongoDB with Mongoid on real projects. Here is how document modelling really differs from ActiveRecord — embedded vs referenced, the missing joins, indexes, atomic writes, consistency boundaries, and where schemaless quietly bites.
Rails 4.0 has just shipped. Here is a thorough, from-the-trenches look at what we are adopting on real projects — strong parameters, the Turbolinks trade-off, Russian-doll caching, first-class Postgres, and the upgrade gotchas.
A talk I gave at the London Ruby User Group introducing Data Context Interaction (DCI) and role injection in Ruby & Rails.
A Ruby design pattern for extending objects with new behaviour at run-time, using modules and singleton classes — illustrated with an RPG character.
Create flexible, scalable 3D-looking buttons with CSS3 gradients and a handy Sass mixin — no images required.