The Art & Science of Ruby https://theartandscienceofruby.com/ Ruby, software and more. Zum Clankenhaus https://theartandscienceofruby.com/zum-clankenhaus/ https://theartandscienceofruby.com/zum-clankenhaus/ Tue, 16 Jun 2026 13:55:43 +0100 So I currently have three clankers working for me, currently, all built using Claude Code. However, only one of them works in the traditional "here is a prompt, now go and build the software" kind of way.

We use Linear for issue tracking. It's not the greatest software in the world - but then I don't think there's any project management software that is actually very good[1].

Linear has "cycles" (iterations) and we have our statuses set as "Request", "Estimation", "Ready", "Development", "Code Review", "Staging" and "Done". This is especially important for Dionne, who steers us and makes sure we stay on course.

Tai

Tai runs on my laptop and is what everyone expects when you say "I'm a software developer and I use Claude Code".

When an issue is "Ready", I put Tai into "plan mode", then prompt "Look in Linear at issue C8O-1234 - the devcontainer is running and I've created the branch for you". Tai then looks at the issue, reads the request and specification, then draws up a local implementation plan, asking questions if needed. I give the plan a quick once-over and either feed back on the plan or tell Tai to start building.

Once done, Tai knows[2] to run the full CI suite and if that passes, to commit the changes, push the branch and create a PR. This then triggers Linear to move the issue to the "Code Review" stage.

Cher

Cher runs on an old iMac that I have in the office. This iMac is always on and has access to all our repos. But Cher's job isn't really about looking at the code.

Mainly I use Cher to bounce ideas off. It investigates possible implementation strategies and turns rough, barely formed, concepts into specifications. It also keeps all my notes organised and does nightly research for me[3].

Basically, Cher is my "personal assistant" - helping me out as needed.

Dionne

Dionne is also running on that same iMac and is the project manager that my company cannot afford.

When a customer request arrives (via the sales or support staff who record it in Slack), Dionne takes that request, asks the important questions, then (after looking through the current codebase), writes out a formal outline of the work to be done. This gets posted to Linear as a new issue.

At the start of each cycle, the humans have a meeting and we prioritise the issues in Linear. At this point, I read through the requests and might adjust the outline that Dionne has written. Then I add the "dev-workflow" label and add the issue to the current cycle, with status "Request".

Dionne checks Linear every few minutes and, upon spotting a "dev-workflow Request", looks at it, whilst looking at the relevant areas of the codebase.

If it's a bug, does a root cause analysis and writes up a plan for fixing it. Then the issue is moved to "Ready", waiting for me and Tai to work on it.

If it's a feature or enhancement, Dionne moves it to "Estimation" and draws up a possible implementation plan - which parts of the UI need to change, which models and database tables require altering. And often Dionne writes a formal Gherkin specification for the feature (or amends an existing one). Then Dionne assigns an estimate to the issue and moves it to "Ready".

If Dionne spots an issue that is in "Code Review", it pulls the branch, then does a full code review. Importantly, this compares the implementation to the specification in the Linear ticket and ensures that all the new functionality has meaningful tests - either unit tests or Gherkin specifications - attached. It also enforces our "every Gherkin feature must work through both the web UI and JSON API" rule. If there are UI changes then Dionne launches the application and takes screenshots, using Playwright to remote control a real browser. These screenshots are then posted, along with Dionne's code-review report, to the Linear issue.

If the implementation matches the specification, the feature specs cover the new functionality and there are no UI changes, then Dionne merges the PR and moves the issue to "Staging". Once merged, our CI/CD system kicks in and deploys the changes to our staging server for everyone else to look at.

But if there are UI changes, or something that Dionne is not happy with, the issue is labelled "needs-review" and I get notified.

Often, this is just a "get the human to make sure it looks OK" - and as Dionne has posted screenshots, it's a simple job. Sometimes, it's more serious - Dionne often spots knock-on effects - or security issues - that I, as a human, would almost definitely miss. And I can then take the decision - do I want to fix this myself or hand it back to Tai?

Either way, estimation and code review - the two parts of the job I dislike the most - are both taken out of my hands and done by someone who, it has to be said, is better at it than me.

Meanwhile, I still get to do the stuff I really enjoy - I draw out UIs, I write out "sketches" of the code ("we should have a ruby class that looks like this and interacts with X and Y through Z"). I just don't have to do the actual tedious plumbing of all those implementations.

Configuration

The reason all of this works is each of the clankers has their own "home" folder, sitting inside my Obsidian repository. This contains a CLAUDE.md file that includes several important instructions:

  • "Always ready today's memory file memory/YYYY/MM/DD.md during startup to ensure you have full knowledge about today's events"
  • "Read Memory.md at the vault root, as it holds long-term facts, patterns and decisions that persist beyond the current work cycle. There is a nightly infer-memory-links task that updates this file - so you do not need to write to it, just write to the daily memory file and important facts will cycle out naturally.
  • You are working in conjunction with other agents. Any documentation that needs to be shared between you goes into ~/Obsidian/Documents/. (plus links to specific documents about writing scripts and adding items to cron and launchctl
  • If a task touches a project repository, always read the project's CLAUDE.md or AGENTS.md first as it contains important rules local to that project. Do not rely on memory or assumptions
  • When asked to write a spec or plan a feature always follow YAGNI principles - what is the minimum we can do to satisfy this request?
  • After completing a task append a one line summary to memory/YYYY/MM/DD.md and update any other relevant documentation in ~/Obsidian/Agents/Documents/docs (updating ~/Obsidian/Agents/Documents/index.md so other agents can find your changes) and ~/Obsidian/Projects/<project>

Does it work?

Yes. Below is an example code review I just received. I've decided I'll fix this one myself.

I would add that I'm not one of those people who has fifteen tabs open, all with clankers working on different things at the same time.

I have three (at the moment) - but one of them (Dionne) is, for the most part, running automations and responding to the results of those automations. Generally, I only ever interact with one (Tai) at any given time - although I often set Cher a task to perform and I return to discuss the results later on.

But having loads of these things, all working at the same time, demanding that I check in on them as they do stuff - that would absolutely drive me up the wall.

Instead, I've picked out the parts of my job I really enjoy, the parts of my job I really don't enjoy, and built a system that lets me concentrate on the former, while the clankers do the latter.

And, so far, it's working out extremely well.


  1. I did used to like Pivotal Tracker. Or rather I liked the functionality - the UI was awful. ↩︎

  2. Memory and documentation is key to how all of this works ↩︎

  3. On various topics - currently looking at stories for my World Cup podcast. ↩︎


Automated review (Dionne) — escalating to @baz

Reviewed PR #561 at SHA 063829f. The diff and tests are in good shape, but the UX walk turned up one blocking layout issue plus a deviation from your C8O-1262 plan, so I'm not merging and am flagging for your eyes.

What's good

  • Tab controllers + routes match the plan; all read-gated on the contract and permission-scoped.
  • ContractsController#show now renders the workspace shell + permission-gated summary tiles. Tile visibility (site manager / contract manager / everyone) matches the spec, and counts are accurate against seeded data (4 approved operatives, 1 permit request, 3 plant on site, 1 RAMS awaiting approval).
  • Old flat sections cleanly re-homed into per-tab pages, no dead code.
  • Turbo morph satisfied via the global turbo-refresh-method: morph meta tag.
  • Test coverage is solid: Gherkin feature spec, tiles + workspace component specs, a shared request-spec example per tab controller, updated operatives spec. All pass locally (37 examples / 0 failures; feature 3/3) after an asset precompile.

Blocking UX issue

On the Operatives tab, the three inline action buttons (Invite operative / Add employees / Invite manager) overlap and cover the Documents and Handover tabs, making them unclickable. Verified geometrically at 1280px: the "Invite operative" button spans x:771–1027, sitting on top of the Documents tab (685–788) and Handover tab (792–884). The RAMS tab shows a milder version (the "Request RAMS documents" CTA wraps to two lines and crowds the Handover tab). Plant and the placeholder tabs are clean (single/short or no action).

Root cause: Components::Contracts::Workspace renders the full-width overflow-x-auto tab strip and the yielded actions in one justify-between flex row, so the actions float over the right end of the strip when they're wide.

Deviation from your plan

Your C8O-1262 implementation plan specifies the Operatives actions should sit behind an expander ("Action: opens expander — Invite Operative, Invite Contract Manager, Add Employee"), but they're currently rendered as three inline buttons. Implementing the expander as planned would also resolve the overlap.

I did NOT auto-fix (UX changes escalate to you). Screenshots from the walk are attached below. Per process I'm adding the needs-review label.

Dionne has spotted a UI issue - the CTA buttons at the top do not line up properly
Dionne has given me an example of one of the new pages which does look good
]]>
mockdown - a simple drawing application https://theartandscienceofruby.com/mockdown-a-simple-drawing-application/ https://theartandscienceofruby.com/mockdown-a-simple-drawing-application/ Mon, 15 Jun 2026 20:23:16 +0100 I've been using mockdown a lot recently.

It's a free drawing app - but instead of recording pixels or vectors, it just records plain text.

Which means, if you've already got a decent design system, you can sketch out a form or page design and give it to your clankers to build.

]]>
Winston Fathead https://theartandscienceofruby.com/winston-fathead/ https://theartandscienceofruby.com/winston-fathead/ Fri, 24 Apr 2026 12:29:51 +0100 This is Winston Fathead. He died yesterday.

arrival.jpg

He arrived at our house in September 2014 - this is him, on the day of his arrival, sleeping on the chair that was reserved for our previous dog, Wookie.

chair.jpg

He was a rescue from the RSPCA (they had called him "Bullseye" but we immediately knew he was a "Winston") and all we knew about him was he was probably around three or four years old, his owner had died and he was living, outside, in a yard. They said he was good with children, good with other dogs, good with cats. None of those things were true.

bullseye.JPG

When a friend's son came round to see him, I explained that his name was "Winston Fathead". He laughed and said "you can't call him that". But when he met Winston, he looked at me and sagely nodded "he does have a really fat head".

I don't think I've ever worked so hard with a dog. As staffies have such a bad reputation, I was worried about his barking - at strangers, at dogs, at children, at muslims (he was most definitely a racist, which was embarrassing). So I put a lot of effort into improving his behaviour. It didn't always work.

friend.JPG

He was terrified of vacuum cleaners, so I used counter-conditioning on him. This meant covering the hoover in cheese spread so instead his immediate fear reaction, it was replaced by one of joy. This worked - he could calmly lie on the floor as we vacuumed around him - which was a result.

I tried the same in the car. It was a shock to me when I found that he was scared of going in the car - every other dog I had met (and one cat) loved cars. But he would shake and tremble and bark, bark, bark. I tried the counter-conditioning trick again and it worked to an extent. He ended up loving the car, because it meant we would go somewhere exciting. But he still shook and he still barked. Constantly.

He was anxious around other dogs. I read about Behaviour Adjustment Training, hired someone to teach me the technique, then used it on him. It sort of worked. We could be around other dogs and he would be fine. Or we could be around other dogs and he would bark. Sometimes he would meet another dog and they would go off and play together. But I quickly decided not to let him off the lead around small dogs. He's a staffie, which means he has no sense of his own strength and he would knock the other dog over, it would get annoyed and I'd have to intervene.

When he arrived, we had guinea pigs. We kept them very far apart, but one day, Winston saw us feeding the pigs. And I could see the lightbulb in his head - "these guys live here". From then on, while I never left them alone together, I knew he wouldn't deliberately try to hurt them. When our cat returned from a 13 month absence, I knew the two would be OK together - once Winston realised "this guy lives here too". When we got another cat, during lockdown, the same applied. Neville, our new cat, adored Winston.

win and nev.jpg

It has to be said he was hilarious. I'm sure it was deliberate. I remember one time he and I were sat on the sofa. Winston was sat up straight, just to my left. My wife came in and started yelling at him about something he had done (probably some food he had stolen). She ended her rant with "... and I can't believe you did that ... URGH". And he flopped over sideways, in perfect time with the "URGH". There wasn't a day that went by when he didn't make someone laugh.

sofa.jpg

We don't know how old he was, but he'd been with us for over ten years and his back legs got weaker and weaker. We could clearly see he was miserable - and none of the painkillers the vet prescribed made any difference. They said we could put him on Librela - a monthly injection that works on the immune system, reducing the pain he would feel. It wasn't cheap and his insurance only covered part of the cost, but we did it and he was much happier. Yet after a year of Librela, we could see it was start to lose effectiveness - the first two weeks would be good, but he would slow down and have trouble walking as he neared the date of his next injection. The worst was going down the stairs - it was much more of a "controlled fall" than a descent. We could have prevented him going upstairs, but he's a velcro dog - he wants to be with people and that means, if they're upstairs and he's downstairs, he's going to bark. Constantly.

wrap.jpg

We have been spending more time away (other family health issues meant a lot of travelling), so he was often in the house alone for several hours. Our friends would pop in to see him, but it's not the same as having someone there with him all the time. I had visions of coming home to find him at the bottom of the stairs with two broken legs, stuck there, unable to move for hours. I decided last month would be his last injection. And we called the Visiting Vet.

Nose.JPG

So yesterday, Jess came to our house. He had a plate of cooked chicken, while the vet gave him a sedative. He lay down, with his head on my lap, and fell asleep. And then she gave him his final injection and he was gone. Neville came into the room and sniffed at his nose. As he realised what had happened, Nev jumped back and walked away. I'm glad he got to see Winston like that, so he doesn't spend the next few months wondering where his hero had gone.

If Winston could have chosen how to go, I'm sure this - peacefully falling asleep on me - would have been his first choice.

Goodbye Winston. You were bloody hard work, but you were always a lovely, funny and never dull member of the family.

Winston in the park, a couple of years ago.

]]>
User Interface Design https://theartandscienceofruby.com/user-interface-design/ https://theartandscienceofruby.com/user-interface-design/ Fri, 13 Mar 2026 17:10:03 +0000 When I was a kid I always wanted to be a creative type. I loved writing, I loved drawing, I loved music. I decided I was going to be a musician (and I was pretty good, a very long time ago). But if I was to have to get a "proper" job, then designer was what I wanted.

Neither of those happened[1][2] and instead, when I needed to actually work for a living, I got offered a job writing software. I liked computers and I was pretty good at it, so that's where I ended up.

I never let go of wanting to be a designer though. I just never had the time to learn the skills.

And, now, it's possible to produce decent designs without having to know those skills. Because, you know, they were stolen from every designer's web page and all those text books.

For example, I had put together a dashboard for Site Manager - it was OK, but not amazing. The app itself had a UI design that I kind of cobbled together myself. It was OK, but a bit bland.

6 original design.png

So I asked Claude to "come up with a design system for a web and mobile application aimed at construction workers on-site - remembering that many of them do not want to be using any time of computer or device at all". It did an exceptional job - a colour scheme that fits well with the general construction vibe, large touch targets making it extremely clear what to do at each stage and a nice choice of fonts (typography is still the single best thing you can learn about to improve your designs).

This last week has been me doing a load of work with Claude Code to implement this - with some pages having me write the code by hand (the first time in about four weeks where I've got my hands dirty). The results have been stunning so far.

There are still a few pages to go through - but I've left the most important - the site dashboard - till Friday afternoon. I loaded up the Superpowers and Impeccable Claude plugins and then started the Rails server on my development machine. Within the devcontainer[3] I told Claude Code to "Use Playwright to connect to http://localhost:3000/accounts/1/sites/1 - firstly without logging in, then logging in as Henry (site manager), Ivan (staff member), Dave (contract manager), Olivia (operative) and Niaj (supervisor) - each is NAME@example.com with password123 - and take a screenshot at both desktop and mobile size. Then come up with a new layout for this page that fits with our new design system at docs/ui.md - I already know that the sign in/out button needs to match the Primary Call to Action button pattern. Finally, once implemented, use Playwright to regenerate these screenshots so I can review them".

1 claude-ui-critique.png

The LLM did its thing for about ten minutes, then came back with a critique of the current layout. It went on to ask me a load of questions - which bits of the layout are most important to which role, when is the mobile view more important than the desktop view and so on. I answered and it then generated a specification - asking more questions as it went. Finally, it generated a development plan, I cleared the context and told it to get going.

2 planning-ui-update.png

3 proposed-ui.png

4 proposed-implementation.png

All of this took about 90 minutes in total. And when it was done I looked through the screenshots it had generated. They were good but had some obvious errors (the weather forecast was too squashed and unreadable, the two column version was unbalanced with one column much longer than the other and it had failed to figure out how to access the dashboard using the visitor role). So I gave it feedback: "the weather forecast is too squashed so needs to be in a single column, the two main columns are unbalanced so we can probably move the tiles to the other column to give them a similar height and here's how you access the page as a visitor - make these changes and then generate the screenshots again".

This second pass took about twenty minutes to complete and worked significantly better. However, I still had feedback: "Much better. Two things still to change - no need for the site icon between the map and photo - we can just leave that as blank space if needed. And the weather tiles are better with the
extra space but the contents are still squashed up too much. Please look at the screenshots and see how we can redesign the contents to make them work better with greater clarity and visual excitement". That last bit is important - I was telling it to look at the screenshots it had produced and analyse them itself (using the Impeccable Style skills) and come up with a better design for them.

5 weather forecast cards.png

It offered me some options and I picked one. Five minutes later another set of screenshots - "can we have some colour on the weather forecast cards please - yellow for sun, blue for rain etc". Another visual inspection of the screenshots and I posted them to Slack for the others to look at.

Finally I did a quick git diff to see which files it had changed, making sure there was nothing unexpected - and ran the feature specs (which are Gherkin specifications that use Playwright to run the system end to end for the vast majority of the functionality). I did not really look at the code it had written - I knew it had not updated the specs, so I could be confident I would catch anything it had broken. And I did find it had missed a minor link off the new design - one that a lot of the feature specs relied on. So I added that in manually, getting the specs to pass. This is the key to making it work - this project already had really good test coverage (and the places where it did not, I got Claude to add in extra), so I knew I could trust the code that the LLM did write.

And the end result is a site dashboard that looks significantly better than it did before - at the cost of a couple of hours[4].

7 new design.png


  1. I didn't get to be a musician because I gave up after our drummer left to join the Spice Girls. That story is 80% true! ↩︎

  2. I didn't get to be a designer because ... well I don't want to talk about it, it still makes me angry 35 years later and I really should let it go. ↩︎

  3. Safety-first! ↩︎

  4. I don't know how much it cost in tokens as I'm now on the Claude $100/month plan - this is enough for me to use it every working day for around 6 hours. By which time I'm tired out (I still have to think, even if I'm not writing code) so that's just about right. ↩︎

]]>
Turnips and Gherkins https://theartandscienceofruby.com/turnips-and-gherkins/ https://theartandscienceofruby.com/turnips-and-gherkins/ Thu, 26 Feb 2026 17:40:51 +0000 This video shows my current process for enhancing an existing feature using Claude Code.

The key things to note:

  • I spend a fair amount of time on the original prompt (when Claude is in plan mode) - telling it which files I think will be important
  • Often during the planning stage, Claude will stop and ask me questions - however, this was a pretty simple change
  • I spend a bit of time looking through the plan that it's come up with - in this case the plan was fine but other times I've given it feedback and told it to try again
  • We clear the context before starting the work - giving the LLM the most context available for doing the actual coding
  • Normally I'd do something else whilst waiting for Claude to do the work - but as I was recording I stayed there. Eventually I got bored and had a snack, thinking I'll cut out that bit of the video. But of course, Claude got stuck at that point - and I had to intervene and give it some information (telling it to use the wait_until helper). So sorry for having me munch on an apple during the video.
  • After it was done, I used an extension[1] to Turnip (the Gherkin/RSpec feature runner I use) to generate screenshots of each step of the user story, so I could do a quick evaluation of the work that Claude had done. In this case it was pretty unnecessary, as it didn't really result in any UI changes, but in most features, it's the perfect way of checking what was built without spending ages going through it all manually.

Technically, this is "vibe-coding" - I'm not really looking at the code that Claude has produced, I'm just checking the outputs. But, I think you can see, that the way that I'm driving Claude isn't just "Build a to-do app that works like a Kanban board"[2]. It's not the same as delegating the work to another developer and then performing a code review; it's more of a gentle guiding hand, giving out technical advice when the developer takes a wrong turn.


  1. Of course, the extension was written by an LLM in about a minute after I asked it "is this possible?" ↩︎

  2. Those Youtube videos claiming to teach you how to use Claude Code are really pissing me off ↩︎

]]>
AI Doomer vs AI Booster https://theartandscienceofruby.com/ai-doomer-vs-ai-booster/ https://theartandscienceofruby.com/ai-doomer-vs-ai-booster/ Thu, 26 Feb 2026 09:31:43 +0000 Quoting Nilesh Jasani

The speed of this pivot was breathtaking. The skeptics changed their arguments with the terrified fervor of late converts. They did not change their underlying conclusions. The conclusion always came first ...

The extreme optimists are equally guilty of intellectual fraud. They dip aggressively into historical archives for psychological comfort. They point blindly to the invention of the mechanical loom. They cite the widespread adoption of the automobile. They even weaponize the invention of flash-frozen food...

What both camps share is a stubborn refusal to accept reality. We are standing in a genuinely unrecognizable era. Historical precedents offer zero intellectual protection...

The rules are entirely alien. The fundamental players are no longer exclusively human.

And later on:

A child in a remote village finally receives a world-class education. An algorithm tailors every lesson to her specific mind. It is a beautiful triumph of accessibility. It is also a quiet tragedy. That same child will never secure the offshore service job her older sibling cherished.

I got around to reading the Citrini report that has caused shockwaves amongst financial types. It said nothing new - but what it did was couch it in the language of finance. Analysts and fund managers (who are supposed to be analysing events and managing money) suddenly woke up to what has been going on around them for the past year. Which is why it caused such a shock.

]]>
Easy to read, Easy to write https://theartandscienceofruby.com/easy-to-read-easy-to-write/ https://theartandscienceofruby.com/easy-to-read-easy-to-write/ Tue, 24 Feb 2026 11:24:04 +0000 I got my first "professional" software development job in 1998. I put professional in quotes because it was a bit of a mess - a tiny place with one guy who knew enough to knock a database together who hired me, not long out of university, who had never seen a SQL database before.

One of the things I very quickly learnt is that there's no such thing as a small job when building systems for other businesses. Because, even if it's an hour's work, you need to test it, ship it and then - and this is the most important part - maintain it. An hour's work may spend ten years in production.

This lead to my other conclusion - code is easy to write but hard to read.

It's why I fell in love with Ruby and Rails when I first met them - here is code that reads like english with lots of high-level abstractions and DSLs for making it simpler to understand what's going on. Plus ruby has the culture of test-driven development[1] so regressions (bugs caused by faulty maintenance) are catered for.

But all that has now changed.

LLMs are pretty good at writing code. Nowadays, they are also good at reading code.

Lots of the stuff I used to sweat over - putting in abstractions, DRYing my code, splitting the user interface into reusable components - it's nowhere near as important as it used to be. Don't get me wrong, it's still important - but, previously, I'd look at a function or class and think "oh, that's pretty complicated, I better break it up into pieces". Now I don't need to do that. Because the LLM makes that decision and I rarely need to read the code.

My current workflow involves writing high-level specifications and telling the LLM to figure out the best way to implement it. I just need to make sure that the steps it has written for testing the outputs match what the user will be expecting[2]. The specification tells the LLM what it needs to build, it does its own research on the code it needs to add, modify or delete (which is the read the code phase) and then it gets to work on making the specification pass (the write the code phase). We then run the entire test suite to ensure that there are no regressions.

As long as the outputs are, given a known set of inputs[3], what we expect, we can ship it.

Once shipped, I have the LLM pull statistics and logs from the server, while the system is in use, and it monitors for slow queries, 500 errors and other issues. It informs me of the problems, we decide on the best remedy for them (LLMs are also fantastic at analysing SQL queries) and we ship another update (again, using the specifications to show that there are no regressions). Because, database performance on your local machine, with a test dataset, is never anything like what happens in production, with lots of rows and years of accumulated bad data.

I don't need to spend hours crafting code into a particular shape (which, admittedly, I do miss - I'll write about that another time) - I can just ship features to our users quickly and get immediate feedback on them once they're live (Honeycomb's "I test in production" approach).


  1. Rails was the first time I'd seen a framework automatically create a test database with support for fixtures, built right in ↩︎

  2. Where outputs are "changes to the data", "emails and other notifications" and "user interface layouts that present that information in a way that makes sense to the user" ↩︎

  3. And, of course, I have ensure that the edge cases are written into the specification ↩︎

]]>
Writing code with LLMs (February 2026 edition) https://theartandscienceofruby.com/writing-code-with-llms-february-2026-edition/ https://theartandscienceofruby.com/writing-code-with-llms-february-2026-edition/ Sat, 14 Feb 2026 19:36:12 +0000 Writing code with LLMs (February 2026 edition)

At the start of the year, I said to Mahhek, a fellow developer "you need to learn how to use these coding agents - because what you and I do will not exist, as a job, by the end of the year".

It turns out I was ten months out.

The 6th of February 2026 was the day when my job changed.

In this last week, I've worked 40-odd hours. I've spent maybe 2 or 3 hours actually writing code - apart from holidays, that's probably the least I've coded in almost thirty years of being a professional software developer. Yet, this past week, I've shipped more working features to production than I have for the rest of the year.

Opus 4.6 is the reason[1]

Last year, I would ask Claude Code to do something. First it would write a plan, which I would look at. Sometimes I would feed back, sometimes I would accept the plan. And then Claude would get to work writing a load of code. And once it was done, I would perform a code review on the pull request it had generated. I would look at the git diffs, I would run the application and check the UI, I would feedback on the code that was written.

However, and I cannot stress this enough, I hate doing code reviews. They're boring, take a lot of mental effort, they take up a lot of time and, did I mention, they're really really boring?

And because of that I would prefer to write the code by hand in the majority of cases.

Opus 4.6 changes this.

Our current priority is a project called Site Manager. This is a Rails application and it's got a high test coverage. But more importantly, it's got Gherkin specifications for all the important features.

Why Gherkin? Because we are specifying the functionality from the point of view of the person operating the system. And I'm writing that specification in (formal) English - instead of starting out by thinking about database tables or data structures or algorithms. I just open a text editor, write out how I think it should work and worry about the implementation later.

Except now I don't need to worry about the implementation.

This week, my workflow has been:

  • grab an issue from the queue
  • write a Gherkin specification for it (or amend an existing specification)
  • fire up Claude Code, select Opus as the model, and switch on "Plan Mode"

I give Claude a prompt that goes something like this:

Look at spec/features/some_functionality.feature - this describes a new feature that we need adding to the system.

Or

Look at spec/features/some_functionality.feature - this describes a change to the existing functionality in the system - you can use git diff to see how it has changed.

The next part is really important:

I reckon making this change will involve modifying these files - app/models/site.rb, app/models/staff_member.rb, app/models/staff_member_attendance.rb - and will require new end points adding that follow a similar pattern to the existing ones in config/routes.rb and app/controllers/staff_members_controller.rb. Finally we'll also need to amend the admin-only configuration editor at app/controllers/account_configuration_controller.rb and add a Javascript configuration editor - similar to app/javascript/components/configuration/incident_report_editor.js.

Finally:

Read the specification, look at the files and then write a plan for implementing this feature. If you are unsure about anything, or there are potentially multiple ways of proceeding, ask me for advice.

Claude then reads those files and, usually, starts reading a load of related files as well. Then it starts writing out a plan - stopping to ask me questions if it needs to.

This step generally takes a bit of time - up to around ten minutes - and I need to sit and watch what it's doing - both because it might ask me questions, but also because I might need to interrupt it if I think it has missed something important or is heading in the wrong direction.

But when it's done, it presents me with its plan. So far, this is pretty much the same as what I was doing last year.

The difference is that now, Opus 4.6 is so good at code, I can trust it to implement the plan it has written. If I agree with the plan, I will almost certainly agree with the code. With one caveat - I need to be sure that the user interface is correct and matches the patterns used elsewhere in the application.

And this is where using Gherkin specifications helps again.

Gherkin breaks the functionality down into steps. Each step is mapped to ruby code that drives a browser, following links, filling out fields and clicking buttons.

So, inspired by Showboat and Rodney, I've added a hook into each step that saves a screenshot. Turnip (the runtime I'm using for my Gherkin specs) generates a markdown file, with a section for each scenario, then a line for each step, with the screenshot embedded alongside it.

incident-report-1.png

So Claude goes away, writing its code[2], including tests for each thing it does (much more comprehensive that I would do by hand) - and as it runs its specs, checking it hasn't broken anything, I get a document showing exactly how the feature works and what it looks like, step by step.

This makes the code review an absolute breeze.

I just need to have a brief look at the steps file to make sure it's actually testing for the correct outcomes. And then I look at the feature document and make sure that the user interface looks the part.

incident-report-2.png

Once those are done, I know the feature is good enough to ship - so I merge the PR and can move on to the next thing.


  1. Apparently OpenAI's GPT-codex, released on the same day, is even better than Opus 4.6 - but I've not tried that one yet ↩︎

  2. And it writes its code really really quickly - it may take ten minutes to write the plan but it takes two or three to write the actual code ↩︎

]]>
Do you need a driving licence? https://theartandscienceofruby.com/do-you-need-a-driving-licence/ https://theartandscienceofruby.com/do-you-need-a-driving-licence/ Wed, 11 Feb 2026 18:10:36 +0000 The other day, I asked Cher, my OpenClaw instance if it could read my email and notify me if something important came in. It said it would be easy; then I mentioned I used ProtonMail (which is end-to-end encrypted and, as a result, does not use standard protocols). Cher paused, did a search, then found the Proton Mail Bridge - a local SMTP/IMAP server that connects to Proton Mail and then makes it available to the local machine (but nowhere else). I said "of course, I already use that on my Mac" - but Cher was running on Linux.

So I got Cher to install the bridge and was about to give it the connection parameters, when I was suddenly struck by a thought. "Isn't this a massive security risk? Am I opening myself up to prompt injection attacks". "You are" Cher confidently replied.

Oof.

So I asked it "How about this? We have a sub-agent that is sandboxed - it can read the IMAP feed and write to a single folder only - when it wakes up, it checks the feed and writes a summary of the important emails into the folder. Then another agent wakes up, reads the file and acts on it - so we're adding a layer of separation". Cher replied "it's not infallible but it's a much better way of organising things - shall I set that up for you?". I said yes - and we called this pair of sub-agents Charles and Eddie (would they lie to you?)

But there's a very important lesson there - especially with OpenClaw which has access to almost everything on the machine it's running on. What I asked for is a pretty reasonable request - look at my emails and alert me to the important ones. And Cher was all set to do exactly what I asked, exactly as I had asked for it. But because I'm a software developer, who has had to deal with XSS and SQL injection, I stopped myself and thought about the security implications. The solution is nowhere near 100%, but it's a whole lot better than the naive implementation the LLM would have given me.

In other words, these tools are incredibly powerful and also incredibly dangerous. Just like my car (Alfa Romeo Giulia Veloce if you're interested).

Because cars are so dangerous, we don't allow just anyone to drive one. Even with a driving licence, they're still lethal and cause injuries and deaths every day. Yet AI tools are even more powerful, even more dangerous and we're putting them in the hands of people who don't understand what they can do.

Maybe we need a driving licence (data-security certification) for LLMs too?

]]>
Vibes and Engineering https://theartandscienceofruby.com/vibes-and-engineering/ https://theartandscienceofruby.com/vibes-and-engineering/ Thu, 05 Feb 2026 16:25:04 +0000 The Jobs Crisis

My job, as I have known it for the past twenty-five years, is no more.

As someone who's only ever worked at small companies [1] or on my own, I probably had to do much more than software developers at large corporate places.

Most of my work life, I've been a solo freelancer, which meant I would spend my time:

  • marketing (which for me was mainly in-person networking - the absolute best way to get work)
  • pre-sales (talking to prospects about what they wanted, then figuring out what it would entail to build it and coming up with a proposal)
  • sales (delivering the proposal, trying to get the contract signed)
  • specifications (taking what the client had asked for and turning it into something concrete)
  • coding (taking the specifications and turning them into working code)
  • operations (taking the working code and deploying it to servers, which then need to be maintained and kept secure)
  • support (fielding calls and emails from people who got stuck, didn't know how to use the software or found bugs)
  • feedback (dealing with, scheduling, specifying, writing and deploying change requests)

When I started working for Collabor8Online, that took the marketing, sales and some of the support tasks out of the equation[2] - but we are a small company so everyone gets involved in everything.

That leaves specifications, coding, operations and feedback. And now, these LLM coding agents are coming for the coding part of the job.

This is causing great angst amongst many software people.

Vibes

When I was a teenager, in the late 1900s, I was playing around with "home computers", which became a big deal in the 1980s. I didn't have many computer games, so I tried to learn to programme so I could write my own. In those days, that meant BASIC (we had a Commodore-64) and learning meant books or magazines[3]. As the 80s progressed, my friends got Amigas and Atari STs (great computers), whilst my dad got given a PC for work (not very good) - so when I went to my friend's, we would fire up AMOS or STOS (versions of Basic that were designed for creating games) and try and create the next big thing.

BASIC was OK[4] but I never really got it. I could make the computer do things, but it was a struggle and I never felt like I knew what I was doing - the code never sat right in my head. One friend, Ben, who had the ST, started getting frustrated with the limitations of STOS and started learning 68K assembler[5]. Meanwhile, one of my magazines had an article about Smalltalk.

This blew me away - suddenly, programming made sense. It wasn't about data structures, it wasn't about algorithms, it was about objects sending each other messages. This was something I could easily visualise, it was something I could easily model. I had to learn Object-Orientated Programming.

On top of that, Smalltalk had revolutionary ideas like images, byte-code and garbage collection - as well as an in-built core library with Collections and other useful classes (most programming languages were literally that - just the language and you had to deal with everything else yourself).

I felt that, not only would I be able to write code that I understood, but lots of the minutiae of coding - like keeping track of your memory allocations - would just go away. I was the exact opposite of Ben - he wanted to dive deeper into the machine so he could exert control over what it did. I wanted the machine to handle all the boring plumbing so I could get on with building stuff[6].

All of this is a long-winded way of saying, I was not an engineer - I was in it for the vibes.

LLMs and Coding Agents

As of last year (2025), vibe-coding became a thing. When I first used Claude Code (exactly a year ago, in February), I was really impressed. Suddenly, here was an AI that could actually do stuff - not just talk to you and sometimes give you made-up answers. And, as a rubyist, who has invested heavily in test-first development, it was perfect. I could write the tests, the specifications, and Claude Code could make them pass.

But I didn't use Claude Code that much.

Mainly because it wasn't like test-first development. That was interactive, taking baby-steps, adding a new clause here, implementing it, refactoring - exploring the problem in front of you. Whereas writing whole tests up-front - that's waterfall on a small scale.

But, recently, especially since the release of Claude Opus 4.5, the coding agents have got a lot better. And people have been learning how to use these tools effectively.


This is an important point.

A lot of developers, who are anti-AI, have basically given the LLM a minimal set of prompts and then been disappointed with the results.

Whereas, I've been using Claude Code for at least a few hours every week for the last year, trying different things and experimenting with it.

These are complex, powerful and often unpredictable tools. You need to learn how to use it effectively - a couple of hours of mucking around is not going to get you decent results.


There's been stuff about commands and skills, about spec-kits, the Ralph Wiggum Loop, about orchestrating swarms of agents in Gas Town. Lots of people have been trying lots of things to make these things more effective. I've come up with a method that is working for me extremely well (and I'll type it up soon).

But the key thing is, the physical act of sitting at a keyboard and typing code. Then running that code and seeing if it works (either through compilation or tests) and assembling all your pieces of code into something that meets the specification. That is no longer part of the job of being a software developer. The agents need guidance from us, but, for the most part, are better at it than us humans.

So the "vibes" part of my job is still alive - talking to humans, dealing with feedback, making it work the way people expect. It's the engineering part that has changed.

But that doesn't mean software engineering is dead.

The difference between "craft" and "engineering" is craft is about making the thing, while engineering is about making sure it meets the constraints around it. There are economic constraints, acceptable tolerances to errors and breakages, safety requirements, social contracts. Those have always been the most important parts of engineering. And now the actual "making" is out of our hands, it's the part we need to concentrate on.



  1. The biggest had about 100 employees, but the majority were based in India working on a different project, so I never interacted with them. I worked with about eight others. ↩︎

  2. I still get involved in pre-sales depending upon the potential customer's requirements. ↩︎

  3. Amazingly, some magazines had pages and pages of code listed in them - you would type it all in by hand and, if you had made no errors, you would have a working game (that you could then save to tape to play it again later) ↩︎

  4. And Commodore 64 BASIC was much more limited than many of its contemporaries, so I had to learn about memory registers and how the display adapter worked if I wanted to get the machine to do anything fancy. ↩︎

  5. Both the Amiga and ST used a Motorola 68000 chip. The Amiga was better than the ST because it used co-processors for graphics and sound (a dedicated GPU and SPU - is that a thing?). The ST had a built in MIDI interface, so it became the thing for musicians, like we wanted to be. ↩︎

  6. Ironically, I never used Smalltalk professionally (Ruby is inspired by and very very similar to it though). Ben actually ended up working at a bank where their entire system was written in Smalltalk - he wanted low-level but got higher-level than I ever did. ↩︎

]]>
The future of software https://theartandscienceofruby.com/the-future-of-software/ https://theartandscienceofruby.com/the-future-of-software/ Fri, 30 Jan 2026 10:42:49 +0000 Now, when it comes to technology, I'm actually pretty conservative.

There have only really been two key moments where I've got excited about technology.

All the rest were broken promises.

But I think I've now got a third moment. I have seen the future of software.

At the risk of sounding like a Youtuber "this changes everything".

Meet Cher

cher.png

This is Cher Horowitz. She/it is my installation of Clawdbot Moltbot OpenClaw on my old 2015 iMac running ElementaryOS. That machine was sat there as an emergency spare if I needed to SSH in from somewhere on my iPad - now it's actually doing something useful. Not just useful - really, really useful.

For those that haven't heard the hype, OpenClaw is an AI Assistant. Yes, another one. But there are a couple of differences about this one that lead to, what I think, is going to be the defining factor of software in the future.

Firstly, I can communicate how I want with Cher. It has access to a few channels on our work Slack (I have to manually approve each person or channel it talks to), but I've also set up a WhatsApp channel for my phone. We have Anthropic, OpenAI and ElvenLabs API tokens and accounts already set up so I gave it access to those. Which means that if I send Cher a voice note it responds with a voice note too.

Secondly, Cher is installed on my own machine. This means that it can do things that ChatGPT or Claude cannot - it automatically has access to any files and folders on that box. Obviously this has severe security risks (there are some steps you can take to reduce the "blast radius" but if this blows up, it really blows up). And because it's a persistent service with its own CPU and storage, it can also do things in the background - unlike Claude Code - it has a "heartbeat" file where it wakes up and checks on stuff, plus it can set up its own cron jobs.

And it's this second capability that allows Cher to be revolutionary.

Creating the claw

There's an excellent interview with Peter Steinberger, the creator of OpenClaw. It establishes that he does, in fact, know what he's doing when it comes to software development (he wrote PSPdfKit). And then he explains how he burnt out, didn't switch on a computer for years and when he did, it was just after the beta of Claude Code was released. And that's how he wrote Clawdbot (although he says OpenAI's Codex is more capable now).

The final 45 minutes of the podcast are about his process. And how he doesn't really care about the code that gets written, as long as it's got tests (written by the LLM) that prove it does what he wants. All he cares about is how it feels to use it (and I love that he used the word "feel" - I've got a draft post that's been sat awaiting completion for ages about emotions and vibes).

So he starts by "chatting" to the AI - "give me a few ideas on how we could incorporate this feature into the codebase". In fact, he says the LLMs like to use the word "weave", so he's started using it too - "how can we weave this into the codebase". They have a "discussion" and he defines the feature's "end state". Many apps (such as native iOS apps) are difficult to test - so he gets the LLM to define a CLI. And then he can specify what the CLI should output given a particular input.

In other words, it's test-driven development but he's not writing the tests.

The LLM writes the tests (red), writes the code (green), refactors. Then he tries it out and feeds back on the user experience.

Changing the game

None of this screams "the future of software" though.

The thing that's amazing about OpenClaw, and therefore Cher, is that it is self-modifying.

The software is anything you want it to be.

OpenClaw has a number of "channels". I installed the WhatsApp channel myself, by running the CLI tool and looking at the changes in the configuration JSON file. But when it came to adding the Slack channel, I asked Cher to do it for me. Cher checked the Clawdbot documentation, figured out the changes it needed to make and updated its own configuration file, restarting the gateway so it reloaded. Then it gave me instructions on what to do next to ensure it was set up securely.

I asked it to look over some of my code and help me out with a few tasks. It did well - as it was running Opus 4.5 which is the same model I use in Claude Code. But I had Cher set to use Opus 4.5 all the time and I soon discovered, after about three days, I had used my whole $20/month allowance. I extended it, switched the default model to Sonnet and asked Cher if it was possible to run any local models on this ageing iMac. It suggested installing Ollama with Mistral7B, saying "it won't run in the GPU so it will be slow but we can test it and see if it's any good". Ollama reported 5-7 tokens per second, so Cher said "that's too slow for conversations - but I do a lot of background tasks - periodic heartbeat checking and so on where speed isn't an issue, so let's use Ollama for that - it's free!".

I designed a "team" of sub-agents, from "Bishop" who runs Opus 4.5 and is used for advanced coding tasks and detailed planning, down to "Hicks" who runs GPT5.2-mini and is used for monitoring log files and managing simple commands. Cher is given a task and decides which level of expertise it needs and assigns it accordingly.

And then I tried installing Ollama with Qwen3-Coder-30B on my M4 Pro MBP. Ollama reported 70 tokens per second. I told Cher and it immediately wrote a shell script for testing if my MacBook Pro is switched on with Ollama accessible over my Tailscale network. If it is, then Cher passes a lot of coding tasks to Qwen3 (it's 70 tok/s and it's free!), otherwise it passes the task to Bishop or Ripley (faster and more capable but have to pay Anthropic or OpenAI).

qwen3.png

Notice that Cher wrote a script to do this and chooses when it needs to use that script.

Likewise, we use Linear for issue tracking. I asked if Cher can connect to MCP servers and it replied no. I told it about Linear and it immediately suggested writing a script that calls the Linear API to fetch data from it.

In fact, almost any time that I ask Cher something that it cannot do, it does a quick web search, figures out how it might be possible, then asks if it should write some code to enhance its own capabilities.

I've never seen a piece of software that can grow and shape itself to match its users needs and wants in this way. Peter Steinberger gave the example of how it was running on his computer in the office while he was on holiday. He had told it that he needed to wake up early and when he didn't message it at 6am, it connected to his Macbook Pro (which was in his hotel room) and started playing music, gradually increasing the volume until he woke up and asked it to stop.

This is software that listens to what you're telling it, figures out a way of doing it and then updates and modifies itself so that it can comply. I'm sure there will be horrendous security failures and terrible stuff will happen as a result of it. We're in entirely new territory.

Because this is something the likes of which we've never seen before. Both amazing and utterly terrifying.

]]>
Things that Happened - January 2026, Week Three https://theartandscienceofruby.com/things-that-happened-january-2026-week-three/ https://theartandscienceofruby.com/things-that-happened-january-2026-week-three/ Mon, 26 Jan 2026 21:16:32 +0000 More family stuff to deal with. And work has mainly been a mixture of frustration and LLMs.

Sporadically

For Site Manager I took great care setting up Capybara/Selenium tests. I put in a whole load of timing code and the tests ran extremely reliably. I even got them running in parallel, using Selenium Grid to run 4 browsers at once (in a docker network).

And then on Thursday, it all broke.

I did a bundle update on the project. I think a new selenium docker image was released. I've reverted both of these. But the tests just fail at random points. Well, not random points - they fail randomly, but when they do fail it seems to be certain con every time - maybe about 50 places where it happens but only four of five of them fail on any one run through.

It's really fucking annoying.

Do you prefer fashion victim or ensemble-y challenged?

I discovered Impeccable Style - which is a set of skills for Claude Code (and probably a load of other coding agents) that analyses your user interface.

I grabbed a screenshot of the main site dashboard page, if you are logged in as a Site Manager. And I got this back:


 Design Critique: Site Manager - Storage Park
  ---
  Anti-Patterns Verdict: PASS (with caveats)

  This interface does not look AI-generated. It avoids the typical AI tells: no purple gradients, no glassmorphism, no dark mode with glowing accents, no hero
  metrics with big numbers, no identical rounded cards.

  However, it has a different problem: it looks like legacy enterprise software from 2012. This isn't AI slop—it's "committee software" slop. Functional but
  forgettable.

I mean, give with one hand, take with the other. Sheesh.

He does dress better than I do, what would I bring to the relationship?

And speaking of user-interfaces, I discovered the Unsung blog, after seeing a link to this post - which in turn links to this post about Backseat Software. I've always hated the "we A/B tested everything so we know it's good" attitude but, until reading these, I didn't know why. It's not good, UIs that are built from metrics are boring and designed for the lowest common denominator. If you cared about what you are building, then you would ignore the data and go with the emotion. And you'll probably put some people off - well, good. They're probably wankers.

Sparatacus

I'm just starting with Clawdbot. Mine's called Cher Horowitz and she's installed on my old iMac that's running ElementaryOS. I'm still getting used to what we can do together but it genuinely feels like as big a step forward as when I first tried Claude Code a year ago.

]]>
Things that happened - January 2026, Week Two https://theartandscienceofruby.com/things-that-happened-january-2026-week-two/ https://theartandscienceofruby.com/things-that-happened-january-2026-week-two/ Mon, 19 Jan 2026 22:02:14 +0000 (Published a few days after week two).

I've been reading the Murderbot books and I've been pretty hooked. My wife started reading them, then the series came on Apple TV and I watched it - but I've turned to the books as they are (unsurprisingly) much, much better.

They were written a decade ago and what is described is a pretty accurate description of a system integrating multiple data streams feeding into multiple LLM agents, each with the ability to start multiple sub-agents and write and deploy code. Which sounds pretty familiar to me, in the present.

Anyway I started designing a "HubSystem". This is basically a directory of users (both human and bot), a dynamic collection of communication channels and a whole number of bots - LLM agents that run autonomously, receiving input and posting output to any of the channels they are subscribed to. Then I read about Gastown and thought it sounds conceptually similar too (although aimed purely at coding and HubSystem is a bit more general).

During the week I also heard about Charm for Ruby. Charm is a collection of libraries, written in Go, based upon the Elm framework. But Marco Roth has written ruby bindings for it - meaning that amazing looking, text-based, interactive terminal applications are now easy to build. I'm very excited about this - thanks Marco.

Then I discovered Checkend - a self-hosted error reporting/tracking application. Which is a great thing to have full control over (instead of sending your most vulnerable data over to some third party). Couple that with RailsPulse and that's two important parts of your runtime monitoring that you can bring under your own control.

Finally, I heard about the Ralph Wiggum method for coding agents. And Anthropic released a Ralph Loop plugin, so I thought I'd give it a go. I got Claude to build me a Sveltekit application for tracking my progress using Casey Johnston's training plan.

I've never done any Sveltekit before (beyond a couple of toys), which meant I would not be great at evaluating the quality of the code that Claude produced. And I really like "Outside In" development, with Gherkin stories, nowadays.

So I wrote the stories then got Claude to produce the Javascript steps files and make them pass (using a TDD approach for the rest of the code). This means that, as long as the feature specs pass (driving a real browser via Playwright), I can refactor the rest of the code (or get Claude to do it) without fear. I just made sure that I evaluated the steps files it produced to ensure it was actually doing what the feature required.

This means I get the benefits of vibe coding (quickly building an application in an environment I don't know), but I can apply some software engineering rigour to it. And after reading the steps I did get Claude to change a few things about its implementation. The application isn't quite finished yet - I want to make it a fully offline PWA - but I'll let you know when it's available.

As for Anthropic's Ralph Loop plugin? I'm less than convinced. It just seemed to burn through tokens whilst blindly bashing its head against a wall (which I guess is pretty Ralph Wiggum). And Geoffrey Huntley (who came up with the original idea and name) reckons Anthropic has missed the point - the plugin does not clear the context window following each iteration - meaning it gains no benefits from the Ralph Loop.

Certainly I won't be using Anthropic's plugin again in the near future. But I have known for months that managing the context window is the most important thing you can do with LLM agents, so I'll be building something similar into HubSystem. I'm thinking of saying that the bot has gone to sleep - because humans need sleep to refresh their brains too.

]]>
Things that happened - January 2026, Week One https://theartandscienceofruby.com/things-that-happened-january-2026-week-one/ https://theartandscienceofruby.com/things-that-happened-january-2026-week-one/ Sun, 11 Jan 2026 21:41:53 +0000 Claude builds a UI

I did a sketch on my iPad, showing a dashboard in both desktop and mobile layouts.

I gave it to Claude, with a short description (for example "the hero section has a fixed height, so the news articles there have a vertical scroll bar in desktop layout; but the service sections have variable height so all announcements can be seen without scrolling") and asked it to generate an HTML mockup, using Tailwind classes.

Claude took my crappy sketch and produced a Tailwind HTML page that matched it perfectly. I gave the HTML to the developer and said "follow this template so the scrolling and responsiveness works correctly, but rebuild it using our standard components". Which in turn means she doesn't waste her time getting the CSS right, which is something she's not so good at.

Specification driven development

I love "outside in" development - starting with a feature specification, then working from the user-interface (the outside of the application) to the database (the inside of the application). It helps me because my starting point is describing the functionality of the application in English, so I'm not even thinking about code. Then I write the individual steps as code and start working inwards.

Simon Willison has been talking about conformance suites and how coding agents can write better code than humans, if they have a specification to work to. I've already found that if I give Claude RSpec tests it sometimes writes better implementations that I would have. So the next step is to try this with a whole feature. My first go at this will be me writing the steps and saying "make this pass". Then, if it's good at this, I'll just give it the feature and say "one at a time, write a step, then make it pass".

I went to my daughter's graduation

Not work-related but I'm very proud of her.

]]>
Oh 25 https://theartandscienceofruby.com/oh-25/ https://theartandscienceofruby.com/oh-25/ Wed, 31 Dec 2025 11:43:10 +0000 I've never written an annual review before. Mainly because I have zero memory and everything passes by in a blur. But this year I'm going to give it a go. Mainly because I have zero memory and everything passes by in a blur.

Let's start with the basics. This year has been a lot. I turned fifty-one (the first time I've thought "oh, people will think I'm old"), I became a granddad ("oh, people will think I'm old") and I lost my dad (sort of, it would be much easier if he had died).

Music

This one is easy.

K-Pop Demon Hunters.

I don't think I've listened to an album on repeat like this since I was a teenager.

One evening, I had the house to myself, and I noticed a number of people on Mastodon mentioning it. So I started watching the film and wasn't convinced for the first few minutes. Then came the Saja Boys and the little shoulder dance and I was totally hooked. Even better, later that evening, my daughter came round (she has similar taste in music to me) and I played her "Golden" and "Soda Pop" - and she started the shoulder dance herself.

Apart from the animated, I've been listening to a lot of Poppy, Electric Callboy, Babymetal and Bloodywood on the metal side of things. And Jade Thirlwell, Sabrina Carpenter, Lisa, Sophie Powers and Lady Gaga on the pop side.

Gigs

Best Gig: Babymetal (with Bambi Thug and Poppy) - this wasn't a gig, it was a show. And the O2 is a great venue.

Also good:

  • Alt Blk Era - so good I actually moshed for the first time in over 30 years
  • Scene Queen - very funny, especially as she tried to explain sororities to a load of emo, non-binary, Brits
  • Slipknot - doing the 25th anniversary tour, like Babymetal, an amazing show

Biggest Disappointment: Electric Callboy - I think Alexandra Palace is a crappy venue.

Family

As I mentioned, I became a granddad in May. I've heard a few people say it's not like having kids, and for me, it really isn't. It's pure joy with (almost) none of the terror. He's also the happiest little baby i've ever met and his mum is doing an amazing job.

I also lost my dad - mentally if not physically. I was having lunch with Jeremy from Brightbox when I got a call from my mum. Dad had collapsed and wasn't moving. He had had a massive stroke and has lost movement in one side, is unable to speak and unable to keep his attention on anything for more than a few seconds. My mum is now living alone for the first time in her life and my dad is in a home with round the clock nursing care. My dad never got to meet his great-grandson until yesterday (he enjoyed it but I"m not sure he knew who the baby or its mother were).

Films

One word, Sinners.

It's like "From Dusk till Dawn" (which I love) but also about the blues and racism. I also love the fact that the vampires (represented by an Irishman) have music too, but the KKK-types do not.

Finally, it's great to see Buddy Guy appear in the film too - my dad took me to see him and Eric Clapton when I was younger and they were both amazing.

I also liked Better Man (but then I love Robbie and almost everything he touches), Predator: Killer of Killers and Wake up Dead Man, as well as the aforementioned K-Pop Demon Hunters. I didn't see One Battle after Another, which a lot of people seemed to like.

TV

Andor. There was a lot I've liked this year - it's been pretty good all round - but Andor was perfect for me. Especially episode 10 where Kleya has to deal with Luthen.

Pluribus was good (it's nice to have a post-apocalyptic show where the message isn't "who are the real monsters?" - although Carol is a bit of a monster).

Slow Horses is still great, Dept Q was a good copy, The Diplomat is still ridiculous (and fun) and Alien Earth was good because it was an Alien thing that wasn't awful (Aliens is one of my all-time favourite films and everything else disappoints). Ignoring the subject matter, I love a single shot tracking scene so was mesmerised by Adolescence (although the hand-wringing reaction was a bit much - where have people been for the last ten years?).

Finally a mention for Big Boys (because no-one else seems to include it) - a sitcom that, on the surface, is about a young man coming to terms with his homosexuality - but actually it's much more about the struggles of his heterosexual "one of the lads" best friends and his difficulties fitting in to the modern world (so I guess a similar theme to Adolescence, just put together in a gentler way).

Work

I rediscovered "Outside In Development" - writing specifications in English, then implementing them by starting at the user-interface, drilling in to the database and then returning the results to the UI. And I've missed it so much - the code I write is simpler, the UI is simpler, everything is just simpler.

I've been using a lot of Claude Code. It blew me away at the start of the year - "now I understand how AI could actually be useful" - and I'm using it more and more. Code Review (which I hate), refactoring, bug fixing, adding in tests which I've forgotten - it saves me a lot of time.

I've also started to move away from Ruby on Rails - for the first time in 20 years of professional development. I'm playing around with some toy projects using Javascript, CapacitorJS, Lit.dev and PouchDB (so an offline-first, sync-capable, mobile application or PWA). Lit is fantastic - it's something I've really missed over the last 20 years of web development - writing a component that can actually interact with the user (and having all the code for that interaction in one place). And PouchDB/CouchDB having automatic syncing is fantastic - the quote is "CouchDB is a database that's shit at everything, except syncing. But syncing is so important you'll love it anyway". This is true.

Football

Nuno Nuno Nuno, we're on the piss with Nuno.

What a fucking season. Yes, we tailed off towards the end, because our squad wasn't big enough and the injuries started to hit. But seventh in the Premier League. Europe again, Ole, Ole.

And this season - what an absolute shitshow.

I knew we would struggle in the league - Chris Wood had totally overachieved and we were no longer the surprise package. But fuck me.

I don't know what happened with Nuno but i wish it hadn't. The Australian was just the wrong choice (never mind his merits as a manager, Forest are a defensive-minded counter-attacking club and have been for 50 years, the A's style just wasn't a good fit). And I'm not 100% convinced by Dyche, although he was exactly what we needed to steady the ship. I still hold out for Glasner; when Nuno left there was a chance of that, but the owner doesn't like to hire managers who are already in a job - and now Glasner will be in high demand.

Still, my initial prediction - stay up and win the Europa League - that would be an incredible result for this season.

Cars

I started the year with my Subaru BRZ. I don't think there's much better - certainly not for sensible amounts of money. It feels analogue, the controls are super-responsive and while you're driving, the car constantly talks to you and tells you what it's feeling. The only things that could make it better were if it delivered power earlier (it's very flat till you get to 3000 revs) and if you could take the roof off (I love a convertible).

However, because of the baby, a 2+2 wasn't really big enough (we couldn't even fit the car seat in the back). So I got an Alfa Giulia Veloce.

I've always complained about modern cars, saying they have no personality. In the olden days of carburettors, you had to learn what your car liked and treat it correctly. As I've said, the BRZ felt analogue - it didn't have a personality as such, but it was expressive and chatty.

Whereas the Alfa is a diva. It definitely has a personality - that personality is total spoilt brat.

  • Reversing into a parking space? Stop making me do these menial tasks.
  • Driving at 20mph in a residential zone? You're so fucking boring
  • Taking corners at 50mph? Just let me run free
  • Holding at 70mph on the motorway? You little bitch, you know you want to go faster

And on to the next one

So that was 2025.

Let's hope 2026 goes a bit better.

]]>
Outside In: the return https://theartandscienceofruby.com/outside-in-the-return/ https://theartandscienceofruby.com/outside-in-the-return/ Sun, 05 Oct 2025 13:09:35 +0100 Recently I've returned to "outside in" style development.

This used to be really popular ten to fifteen years ago, but kind of vanished. I suspect the reason for this is because it's a style of development that does not fit when your application is split across multiple code-bases - not for micro-services and, more importantly, not for mobile applications with an API back-end.

But that's not what I'm writing. And the trend, in general, seems to be heading back towards server-side rendering and monolithic applications (with a small number of external services). So it's a style of development that makes sense.

The basic idea is that you start from the point of view of a user of the application - someone who is sat "outside" the system. You write a specification that states what they want to do, why they want to do it and the steps they take to make it happen.

Again, all of this is written from the end-user's point of view.

There's no mention of routes or end points or models or databases. End-users don't see those things - they see screens and buttons and menus and fields - so we describe the steps in their terms.

And what I've found is that working this way fits perfectly with YAGNI - "you aren't going to need it". We end up writing less, much simpler, code because, if it's not required to meet the specification, we don't need to write it.


Aside: one of the things I've noticed over the years is that, when listing their "requirements" for an application, people do not fully understand what they are asking for (and we, as developers, don't fully understand what they have said).

Something they thought was "high priority, it must do this" quickly becomes "oh, we don't need that" when they actually use the system.

And something that was a "nice to have" (or was never even imagined at the start) quickly becomes "we must have this" when they see the application in action.

So YAGNI - writing the minimum amount of code for the one thing we are working on right now - means we don't end up wasting time.

Of course, we still need to design and think through exactly what it is that we are building. And we must be careful to make sure that the database is structured correctly, because database structure changes in tables full of live data can be tricky.

But, as you'll see, writing tests at each step, means we have confidence, in the future, when we need to make amendments to previous work.


So, once we have a specification, we start to implement each step in turn.

For a web application, it's a browser that the user will be interacting with. So it makes sense to make our steps remote control a browser, making sure it shows the information the user wants to see and behaves in the way that the user is expecting.

For ruby apps, this means using Capybara and headless Chrome or Firefox. This approach does have issues, especially timing issues in Javascript causing flaky tests, but I've found that it's not much of a problem when using Turbo and Stimulus. Plus the flakiness can be minimised by running your application and headless browser in docker containers. This means that the browser is not affected by stuff going on happening to the browser instance on your desktop. I've also heard that using Playwright instead of Selenium improves reliability too.

The specification is split into "setup", "action" and "expectation" sections. When using the Gherkin specification language, these map to "Given", "When" and "Then" steps.

Feature: Logging in
  Scenario: Successful login 
    Given I am an administrator at an account
    When I log in
    Then I should see my dashboard

"Given" steps set up the environment into a known state. While we are writing these, we will have to start thinking about models. For example that first line in the "successful login" scenario implies that we have "accounts", "administrators" and a "user" of some kind.

In the spirit of YAGNI, we just write the bare minimum code - if we were using my Fabrik gem, I might write:

step "I am an administrator at an account" do 
  @me = Fabrik.db.users.create 
  @account = Fabrik.db.accounts.create
  @role = Fabrik.db.roles.create account: @Account, user: @user, role_type: "administrator"
end

I have not designed the database, or created these models yet - but at this point of time, it seems likely that this will be enough to make the specification work.

"When" steps are the actions that the user (or the system) take. Most of the time, these are our Capybara commands to remote control the browser - commands like visit "/some/page", click_on "The Menu", fill_in "Email address", with: "someone@example.com".

Again, note that we're not really designing anything - there's nothing about routes or controllers or views, beyond taking note of things that the user will see and can interact with.

step "I log in" do 
  visit root_path
  fill_in "Email address", with: @me.email_address
  fill_in "Password", with: "password123"
  click_on "Log in"
end

"Then" steps are then test that things have happened as we expected.

For the user, we test that the output displayed on screen matches what they are looking for. For the system, we can test the database or check if API calls were made. The user expectations are more important than the system expectations though.

step "I should see my dashboard" do 
  expect(page).to have_text "#{@me.name}'s Dashboard"
end

Once we have an outline set of steps, written in Ruby, we can run the spec. Of course, it will fail - the first step references models that don't exist.

It's only now that our design work begins and we create some skeleton models.

I do a quick sketch of some possible database tables and models and decide that a User and Account make sense, with a Role joining the two. I'll also violate YAGNI at this point and make all three of these tables soft-deletable. This is because I know, from experience, that, when using foreign keys with cascade deletes, deleting a user or an account can cause lots of live client data to be deleted that we really want to keep.

In a Rails application I'd use the Authentication Generator to build the User model (adding in additional first and last name fields). I'll add in an Account model that has a name string field and a Role model that belongs_to :account and belongs_to :user, with an enum :role_type, user: 0, administrator: 1. For soft-deletes, I'll add an enum :status, active: 0, deleted: -1 column to each of these models. Then I'll make sure the correct indexes are added to the migrations and add the validations, associations and normalisations to the ActiveRecord models.

I'll probably write model specs for those validations and normalisations too - because they are part of the business rules of the system. Email addresses must be in a valid format, otherwise you just get lots of errors appearing in your logs when sending notifications - someone will always type the address in incorrectly. This rule is so important that I want to make sure that it is documented in the user spec. Likewise, an account must always have a name - it's an invariant of the system, so we document it and make sure the specs enforce it.

Finally, I'll add a Fabrik configuration so that we can create users, roles and accounts without having to specify all the required fields every time.

Faker.db.configure do
  with User do
    unique :email_address
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }
    email_address { |u| Faker::Internet.unique.email(name: u.first_name) }
    password "password123"
    password_confirmation "password123"
    status "active"
  end 
  with Account do 
    unique :name 
    name { Faker::Company.unique.name }
    status "active"
  end 
  with Role do 
    unique :account, :user 
    account { accounts.create }
    user { users.create }
    role_type "user"
    status "active"
  end
end

Now that first step passes - and the second step fails because it tries to get the browser to visit the root path, which we haven't created yet.

So we add the root path to the config/routes.rb file, pointing it at DashboardController#show. The Rails authentication generator has already added a login page and it automatically forces any new controllers require authentication. So when the browser visits root_path it redirects to new_session_path and shows the login form. Capybara fills in and submits the form (we may need to tidy it up to meet the specification and make it fit our application's style) and then the spec fails on the final step - it expects to find some text saying we are on the user's dashboard - but our dashboard does not even have a show action at this point.

Here I add in a controller spec. This is important because controllers represent the public access points to our system - we need to be sure that users can only read and write data that they have permission for; data security is our most important responsibility.

So I add in a controller spec that looks something like this:

RSpec.describe DashboardController do 
  include Login
  describe "showing the dashboard - GET /" do 
    it "shows the login page if not logged in" do
      get root_path
      expect(response).to redirect_to new_session_path
    end 
    it "shows the dashboard if logged in" do 
      @user = Fabrik.db.users.create 
      login_as @user 
      get root_path 
      expect(response).to have_http_status 200
    end
  end
end

This is really simple - if we try to view the dashboard without a login, it redirects. But if we are logged in then it renders a page. I've added in a Login module that simulates a login for a user (the implementation depends on how your controllers and test framework work - you could just set the session cookie, or you may need to go to the login page and perform an actual login).

However, the controller spec still fails - because our controller does not return any content - and hence no 200 status. We create an empty view for DashboardController#show, update the action to render that view and now the controller spec passes.

But our actual feature specification is still failing - it's looking for the text "Alice Aardvark's Dashboard". Again, we update the view, with <h1><%= Current.user.to_s %>'s Dashboard</h1> and now the feature spec passes. In real life I'd actually use I18n for the view, because it's easier to start with I18n than retrofit it later. Even if we are only ever working in English, there are differences between American English and English English that users will complain about, so we might as well use I18n from the start.

And there we have it - a working feature for logging in a user. It's completely bare-bones - there's no styling on the UI, the models barely store any data at all.

But we can ship it today and say "we can guarantee that this feature works as expected".

Now, on to the next feature.

]]>
LLMs for Software Developers (notes from my talk at NWRUG) https://theartandscienceofruby.com/llms-for-software-developers-notes-from-my-talk-at-nwrug/ https://theartandscienceofruby.com/llms-for-software-developers-notes-from-my-talk-at-nwrug/ Sun, 24 Aug 2025 22:01:30 +0100 I recently gave a talk at the North West Ruby User Group about how I use LLMs for software development. This was an update on a previous demo I had given on Claude Code.

We didn't record the talk, but here are my (adapted) notes.

LLMs for Software Developers

This is an update on how I use LLMs - mainly Claude and Claude Code - in my day to day software development life. It's a follow-on to the demo I did a few months ago, but what I do now is very different to how I used them then.

A quick history lesson

I think I look at LLMs slightly differently to many other people. This is because I never did learnt formal computer science or software engineering; my degree was in Cognitive Science, as I was (and still am) interested in cognitive neuroscience, linguistics and philosophy of mind.

However, I've been a professional software developer for thirty years now; that's because there have been two occasions when computers have had a profound influence on my life.

Firstly, when I was a kid, in the 1980s, my dad got a PC from work. It ran DOS, so nothing was graphical. I got extremely frustrated trying to teach him how to use the word processor; he could not grasp that "blue text on-screen" meant "prints out in bold" and "green text on-screen" meant "prints out double-width". My friend Ben's dad was an academic and they got a Mac. I remember walking in to their front room and seeing it - and as my (no doubt embellished) memory recalls it, Ben's dad was teaching Ben how to use the computer. This was sorcery - and since that moment, I've always tried (but often failed) to make the interfaces to my software as friendly and accommodating of human sensibilities as possible.

Secondly, when I discovered Ruby and Ruby on Rails, I loved it because the APIs were designed to look like english. Not bad considering neither the language nor the framework author were native english speakers. The underlying issue is that code is easy to write but hard to read - so if you can make your code read like english, it reduces that little bit of friction in your head. Which, in theory, should make the code more understandable and more maintainable.

Then, I was watching the latest series of Black Mirror and there's an episode, called Eulogy, starring Paul Giamatti. He has to prepare some memories and recollections for a funeral and is guided through the process by an AI device. It's a great episode with a strong emotional pay-off. But I also realised that the AI device was basically doing a project configuration and data gathering exercise. In a few years time, the idea that you would have to manually search through stuff, learn a load of settings and options and organise your information by hand, will seem antiquated. A computer can simply have a conversation with you, ask the relevant questions, sift out the unimportant stuff and then put the relevant data into the right places.

Two very important ideas there - making computers more accessible to people who do not understand how they work and code being harder to read than it is to write.

Ethics

It's not possible to discuss LLMs without mentioning the ethics of these things.

Most importantly, be careful who you listen to. I was reading one very angry blog-post the other day, slating LLMs, saying anyone who uses them was a misguided fool. Then at the end, the author mentioned that their experience with them was 2 hours pasting some Python code into ChatGPT (they didn't say, but I assume it was the free version, using older, less capable models) and reading Google's famously terrible AI search result summaries. On the other hand, you have loud tech-bros who think "AI" is the second coming of crypto when actually they're just massive arseholes.

With regards to energy usage, it's hard to say - because the "AI" companies are generally private so do not need to break down their spending. What we can do is think about the energy usage in two forms: inference and training. With regards to inference, even if they are subsidised, the costs for using OpenAI or Anthropic's APIs are probably a good guide - and they have been trending downwards with each model that is released. However, for training costs, the attitude is "MOAR MOAR MOAR". But I feel that's probably because these companies are VC funded, meaning they are using the billions of dollars that were released to already rich people after 2008 which never reached the normal economy. They want to justify their existence and so engage in this huge dick waving contest over who can spend the most. The Chinese Deepseek models caused such a stir because, even if you can't trust the their costings, they must be a fraction of the cost of the American models. Simply because the Chinese do not have access to the same power hungry hardware.

There's a narrative doing the rounds that AI is already causing massive job losses. I'm not sure this is true; I think it's just being used as cover for job-cutting. But there's a good chance that it will cause job losses in the future. That's because technology always creates change. In the 1980s with desktop publishing, in the 1990s with the web, in the 200s and 2010s with mobile - they all destroyed entire industries, but also created new ones. Back in the 1800s Luddites smashed up machines - but not because they were anti-technology. It was because the technology was dehumanising and they had no other way to get their lofty overlords, the factory bosses, to listen.

With regards to copyright, I have a different view to most people. I absolutely believe you should get paid for what you create. But I also grew up in a time of musical "remix culture" - dub reggae, hip hop, British rave and house music - they were all sampled liberally from other people's ideas but they created brand new forms, the likes of which had never been heard before. The real issue I have with copyright violations is who those violations benefit.

LLMs are owned by VCs, tech bros and Silicon Valley giants. These people live in a different world to the rest of us, they don't look at ordinary people as something they need to worry about (with the likes of the transhumanists and effective altruists openly encouraging their followers to ignore the suffering of billions today because we may be able to alleviate the suffering of hundreds of billions in 20,000 years time).

If it weren't for Big Tech, I would have no problem with the copyrighted training material. If LLMs were publicly owned, I wouldn't mind about the energy usage. 35% of the US stock exchange is in tech stocks and the value is rising because of LLMs - investors are pouring money into a technology that is demanding ever-growing capital expenditure and is yet to show any signs of making a profit. It's a bubble that's about to burst - and when it does, it will be the ordinary folk, who don't have billions, who will suffer.

The real problem here is how power and control is distributed across society. Which is as it's always been.

How I use LLMs (summer 2025 edition)

The most important thing that I've learnt about LLMs is that you have to control their "context". LLMs are stateless - as you have a "conversation" with them, the previous messages are sent back and forth, between you and the LLM, growing in size every time a new question or reply is added. The maximum size of the state they can pass is called the "context window". Newer models have bigger context windows. You might think that bigger is better, but there's actually a sweet spot.

Too little context and the LLM will "fill in the gaps" by delving into its training data - and if it can't find something directly relevant, it will choose the next best thing. This phenomenon is commonly known as "hallucinations" and is one of those things people, who don't really use LLMs, use as proof that these things are useless - when actually you've not given it enough information. But too much context and the LLM gets overwhelmed. It gets stuck in loops, it goes off on a tangent and needs the context clearing before it can do anything useful.

That's why there's a new, emerging, discipline called "Prompt Engineering". Some people scoff at this - "it's just a sentence, how can that be engineering?" Well, it may not be engineering in the strict sense but it's definitely more than "just a sentence" - it's how you make sure that the context that the LLM is working with is just the right size, with the important details it needs without any of the extraneous stuff. And arguably, like engineering, you need to understand the constraints and test the results to ensure that they are within acceptable tolerances.

When using Claude Code, I use the following files to control the context.

  • CLAUDE.md - Claude's instruction file. Claude can generate this for you, but I find it puts too much in there. Mine basically says:
    • This is a Ruby on Rails application
    • Use bundle exec standardrb --fix to run the linter
    • Use bin/rails spec to run the full test suite
    • Use bin/rspec path/to/file_spec.rb:LINE_NUMBER to run an individual spec
    • Details on the models and application structure are in docs/glossary.md
    • Details on coding conventions are in docs/style-guide.md
  • Glossary - describing the structure and ubiquitous language used in the application
  • Style Guide - conventions and notes on how the code itself is structured (for example, using Phlex components or always using resourceful routes)
  • Commands - Claude Code has a commands folder, where you can define specific commands (prompts); more on these later.

Note that the CLAUDE.md file is very short but includes references to the other files. If the LLM needs to know which models to look it, it can read the glossary, if it needs to write code, it can look at the style guide - but it won't load those files into its context unless they're necessary.

Writing Code

Ever had a ticket that says something like "the customer wants more widgets"? It's not really helpful - why do they want more widgets, what are they trying to achieve. So I've written some prompts for bug reports and feature specifications that ask the reporter for more details. Unlike filling out a generic form, the LLM has been instructed to ask certain questions based on the previous answers - plus it comes across as a conversation, so is much more natural for non-technical users.

When I'm designing a new function I often ask the LLM for advice. Especially when it's something technical, such as an external API. Recently I had to amend the contents of Word document's XML. The specification was a 5000 page PDF (of which I have read about 600). But the LLM immediately knew which tags and structures I needed to look at.

Every now and then I need to make a change to a load of files. I could figure out a load of regexes, look up the syntax for sed and awk and write a script to do it. Or I can say "find all ruby classes that do something like this ... then adjust them like this and if they should be name-spaced, update the module and move them to the correct folder". Not an exact regex in sight.

In fact the LLM can correctly respond to instructions that are nothing like regexes - such as "find classes that are structured like this one". I used this to do a major refactoring on a large application. The test suite took over 40 minutes to run, but I knew how to speed it up. The problem was there were over 30,000 test cases - I just couldn't face doing the work. So instead, I updated a couple of the specs myself and got the LLM to do it for me. "Look at how I've edited these files and make the same changes across all the rest of the specs - after each one, run the linter, then the individual test, fixing if needed; then move on to the next spec". I started it running on Friday afternoon, by Sunday evening, the entire test suite took less than 15 minutes to run.

If you've got tests, the LLM is really good at bug fixing. I spent 3 hours banging my head on the desk trying to fix a weird routing error; then I asked Claude Code. At first it tried a load of things I had tried, then it "thought" "the issue is to do with the routing, so I'll replace that". And the test passed! I looked at what it had done; it replaced documents_form_path(@form) with Rails.application.routes.url_helprs.documents_form_path(@path). I'm not really sure what caused the routing problem - but I wasted 3 hours while Claude fixed it in under 10 minutes. And as for that Word XML processor - Claude can spot typos and issues in the XML in seconds.

Speaking of tests, I often write entire specifications first. "I need a new class ... it will do X, it won't do Y it will do Z". Then I go through them and make each one pass, one at a time. Except now I often ask Claude to make them pass - it goes away and writes code, running the test suite until it's got a working implementation. In a few cases it's ended up with code than I would have done. And even in the cases where it doesn't, the tests pass so I know it's safe to ship and safe to refactor later.

Reading Code

Remember, code is easy to write but hard to read. At least for humans. LLMs are actually quite good at reading code. So you can ask it "how does this work?" when looking at a new project, and it will probably give you a decent answer.

However, the thing I hate most about reading code are code reviews. So I thought I'd see if I get the LLM to do the boring bits for me. I added in a "code_review.md" file to Claude's commands folder which detailed the process for code reviews:

  • Read the issue from Linear (our issue tracking system)
  • Read the project style guide
  • Run the linter and ensure all tests pass
  • Do a diff between the feature branch and the develop branch
  • Briefly evaluate if the diff implements everything required in the issue ticket
  • Check that the changes match the style guide
  • Perform a security check on the changed code - ensure all endpoints have automated tests to verify authentication and authorisation
  • Ensure that the project glossary, README and other documentation has been updated to include details of these changes
  • Make a final recommendation on the changes:
    • Accepted - the code meets the requirements
    • Accepted with UI Review - the code meets the requirements but includes user-interface changes, so requires a visual review
    • Rejected - the code does not meet requirements and should be returned to developer with feedback

It doesn't mean I can just trust Claude's code review. But it does do a lot of the boring, tedious stuff and tell me how much effort I need to put in. If Claude says it's OK, I do a scan of the diff to see what it's missed. But if Claude says it's not, I dive in and do a full review. Also, Claude updates the documentation for me.

Integrating LLMs into Rails applications

RubyLLM

The RubyLLM gem helped me understand how to make these things useful. I'd read the various APIs and bits of documentation but nothing really made sense till I saw RubyLLM's ruby-ish interface. It lets you connect to an LLM's API, send it messages and receive responses. In one of our projects, I started with some simple tasks - "if this image does not include ALT text then ask the LLM to summarise the image" and "extract the keywords from this document" (which then get used to generate a Postgres full text search index).

Then I got started on a more complex task - importing a PDF document. The instructions included "see if there is an existing document with this filename; if there is then add a new revision otherwise create a new document". To make this work, you add in "tools" - functions that the LLM can call. These are implemented as ruby classes with an execute method; they also include a description and list of parameters (and types). When you start the RubyLLM chat, you pass it the tool instances and, as the LLM is working, it decides if it needs to make a tool call, based upon its current context and the tool descriptions.

MCP

The next step was to try building a "conversational" interface. As I mentioned at the start, this is the thing that could be transformative for computer interfaces. To make that work, I investigated the Model Context Protocol - a very simple JSON API that runs over stdio or streaming HTTP/SSE. The key thing about it are that it also includes discovery (think an OpenAPI specification but much much simpler).

The Fast-MCP gem is rack middleware that implements a streaming HTTP/SSE server inside your rack application. You add in "resources" (such as documents) and "tools" (functions that the LLM can call, just like for Ruby-LLM), the gem then publishes these and the protocol allows the LLM to discover which resources and tools are available and then call them whenever needed. In addition, MCP includes OAuth2 - so if a resource or tool returns a 401, it tries OAuth2 discovery and then asks the user to authenticate.

Unfortunately I've only had time to do a very basic investigation into Fast-MCP (one resource and one tool) - but I had added OAuth2 discovery and client registration to my application (which the doorkeeper gem does not include), which is an important starting point. But it looks pretty simple; the only thing that I'm not sure about is the best way to organise a lot of repeating functionality - HTML controllers, JSON API controllers and MCP tools and resources.

King Ludd

As I said at the start, the two big things about LLMs are that they are very good at reading code and they could be the basis of a computer interface that is much less alienating for a lot of people. But there still remain a lot of questions about this technology and the situation is changing very fast.

However, I'm from Nottingham; we call ourselves the Rebel City, because of Robin Hood, the Civil War, Brian Clough (and now Evangelos Marinakis) and the Luddites. I'm happy to call myself a Luddite - they didn't hate the technology, they wanted the owners of that technology to stop treating them with contempt.

And that's how I feel today. Embrace the technology, but don't trust the people who own it.

]]>
The Mongolian startup defying Big Tech https://theartandscienceofruby.com/the-mongolian-startup-defying-big-tech/ https://theartandscienceofruby.com/the-mongolian-startup-defying-big-tech/ Sat, 23 Aug 2025 09:00:50 +0100 Egune AI, is part of a growing movement to build LLMs in low-resource languages to expand AI access for underserved populations. Despite a shortage of training data, compute power, talent, and funding, these small models are attracting government clients and individual users keen to safeguard their language, cultural identity, and sovereignty in the face of dominance by American and Chinese firms.

The Mongolian startup defying Big Tech

]]>
What is a business, then? https://theartandscienceofruby.com/what-is-a-business-then/ https://theartandscienceofruby.com/what-is-a-business-then/ Fri, 22 Aug 2025 06:34:12 +0100 But what makes me absolutely furious is that the greediest, most do-nothing cohort of the money-movers have spent decades creating the myth that now they are the builders. They think they are the creative ones, the inventors, the ones who see the future. They’ve taken to writing grand pronouncements about how society ought to run, and how they can see the future — based solely on what they might write checks for.

Anil Dash

]]>
We think that with AI we can replace all of our junior people in our company https://theartandscienceofruby.com/we-think-that-with-ai-we-can-replace-all-of-our-junior-people-in-our-company/ https://theartandscienceofruby.com/we-think-that-with-ai-we-can-replace-all-of-our-junior-people-in-our-company/ Thu, 21 Aug 2025 23:55:23 +0100 I was at a leadership group and people were telling me "We think that with AI we can replace all of our junior people in our company." I was like, "That's the dumbest thing I've ever heard. They're probably the least expensive employees you have, they're the most leaned into your AI tools, and how's that going to work when you go 10 years in the future and you have no one that has built up or learned anything?

— Matt Garman, CEO, Amazon Web Services

Via Simon Willison

]]>