RubyLLM 2.2: The AI Framework for Coding Agents RubyLLM creator Carmine Paolino released RubyLLM 2.2, an update to the Ruby AI framework built specifically for coding agents, after running a four-way test in which Codex on GPT-6 Astra built the same streaming chat app 11 times over — with RubyLLM on Rails, LangChain, the Vercel AI SDK, and plain Ruby with no framework — validated by 198 checks across providers. Paolino reported that without a framework the agent hand-signed every Bedrock request with AWS signature version 4 and decoded Amazon's binary event stream frame by frame in 93 lines for one provider, versus four lines of RubyLLM configuration, and that a 21-iteration feedback loop of watching agents stumble and fixing the gem, generators, docs and skill surfaced seven real bugs including missing Anthropic and Bedrock caching and a stale cancel that killed the next reply. The release ships a skill inside the gem so coding agents can find RubyLLM's way of handling streaming, tools, caching and token counting across 40 features and 23 providers. “You don’t need frameworks anymore.” “Just let the agent write it. It’s one prompt away. Why would you depend on somebody’s gem?” How many of you heard this this year? Yeah. And it’s spreading. My friend Andrzej Krzywda https://x.com/andrzejkrzywda/status/2112865294075265072 goes to every conference and talks to everybody, and a few weeks ago he wrote that 40% of devs try to reinvent RubyLLM. Forty percent That’s one person’s impression. But the idea is in the air. You can feel it. So here’s what I think about that, dear Ruby developers. Bullshit. Frameworks matter. And I won’t just say it. I tested it. Same Agent. Same App. I took a coding agent, Codex on GPT-6 Astra, a really good one. And I gave it the same app to build four times. With RubyLLM on Rails. With LangChain. With the Vercel AI SDK. And with no framework at all, plain Ruby, “just let the agent write it”. A real chat app. Streaming, conversations, cancel, cost. A second provider you can switch to in the middle of a conversation. Fallbacks. Surviving a restart. Approvals. Files and a sandbox. MCP. Then Gemini, Azure, Bedrock. Eleven steps. And after every step, a test suite reruns everything, on every provider. 198 checks at the end. Here’s what I learned. The agent writes it. Streaming on Bedrock? Done. Tools on Gemini? Done. Honestly, they’re amazing at it. Then you own it. Every feature, on every provider. Every provider streams differently. Calls tools differently. Caches differently. Counts tokens differently. Forty features. Twenty-three providers. https://rubyllm.com/provider-coverage/ Want to see one feature for one provider? With no framework, the agent signed every Bedrock request by hand, AWS signature version 4. Then it decoded Amazon’s binary event stream by hand, frame by frame, checking the CRCs. Ninety-three lines. For one provider. It works. You own this now. Forever. And with RubyLLM: config.bedrock api key = ENV "AWS ACCESS KEY ID" config.bedrock secret key = ENV "AWS SECRET ACCESS KEY" config.bedrock region = ENV "AWS REGION" config.bedrock api base = ENV "BEDROCK BASE URL" Four lines of configuration. Same agent wrote them. And then Bedrock does approvals, fallbacks, caching, cancel, everything the app already did. So frameworks matter. Case closed? Not quite. Frameworks Matter. Only If Agents Can Use Them Well. An agent takes the easiest path it can see. If it can’t see your framework’s way, it writes its own. Then your framework is dead weight: a dependency, and the agent’s own code on top. And that’s exactly what happened in my first runs. RubyLLM installed, my own skill loaded, and the agent still: - Wrote its own price table. With the wrong price RubyLLM keeps a usage ledger, and chat.cost reads it. - Patched the gem to count usage. RubyLLM already sends an event for every call. - Rebuilt HTTP cancellation by hand. RubyLLM has chat.cancel . - Read 280 thousand tokens of my source code. To find things the guides already said. The agent isn’t dumb. It couldn’t see the RubyLLM way from where it stood. So I showed it. The Loop Agents build the same app, on RubyLLM main. I watch where they stumble. Or where they write code they shouldn’t need. I fix it in RubyLLM. The gem, a generator, the docs, or the skill. Then I run it again. And again. Twenty-one fixes. And it found more than stumbles. It found real bugs: - ~~Sandbox couldn’t see your files~~ - ~~Agent refused another provider’s model~~ - ~~Stale cancel killed the next reply~~ - ~~One huge tool result broke the chat~~ - ~~No caching on Anthropic, Bedrock~~ - ~~Refresh lost messages~~ - ~~Dead worker held its chat~~ If you had written your own, you would have every one of these bugs right now, and you wouldn’t even know. Built for Coding Agents The new user of a framework is a coding agent. So I spent a ton of time on that this cycle. As much as on features. RubyLLM ships a skill, inside the gem, so it matches your version. And to install any skill just using Ruby, I made a new gem, skill https://github.com/crmne/skill : gem exec skill add "$ bundle show ruby llm " --skill rubyllm --agents coding One command, and Claude Code, Codex, Cursor know the RubyLLM way. It works like npx skills . But it also adds skills to RubyLLM itself, in your Rails app, with a skills.lock . Gemfile.lock, for skills. The docs are written so an agent finds the answer. llms.txt , Markdown for every page. And the API stays small on purpose. One verb per meaning. Every public method is in api.txt , and CI fails when it changes without me. Every extra name is a wrong guess an agent can make. And every fix goes out to everyone. One bundle update . That’s what a framework is. Let’s Count Code you own at the end. Lines the agent wrote itself, averaged across its attempts. Tests don’t count. Generated code doesn’t count. | | Lines | |---|---| | RubyLLM on Rails | 246 | | Vercel AI SDK | 748 | | LangChain | 962 | | No framework | 1,070 | And every provider you add? Lines that speak one provider’s format: RubyLLM, zero. Without a framework, up to 97. Every. Single. Provider. And yes, I tuned RubyLLM against this benchmark. On purpose. So here’s the real test. A second app. A support desk, with documents, a sandbox, MCP tools and approvals. No agent saw it while I tuned anything. About 420 lines of app code with RubyLLM. 867 without a framework. Half the code. Same Bill. Half the Code. Now let me be honest with you, because you’d find out anyway. It wasn’t cheaper. On the chat app it cost less, about $12 against $17 or more in earlier runs. On the support desk it cost more, between $14 and $19 against $13. Same bill, give or take. The agent still reads a lot, and you pay for every read. The new skill in 2.2 already cut the chat app’s bill by about a quarter, and I’m not done. With Claude Opus it was 1,088 lines against 2,969, and the RubyLLM run cost more, $19 against $11. I’m not going to stand here and lie to you about it. One model at a time, one task each, a handful of attempts, fake providers in the tests. Take it for what it is. Half the code. And think about what happens after the agent is done. The Agent Leaves. The Code Stays. After you’ve used it, the agent is out The accountability of that code stays with you. A thousand lines of it, in your app. And providers change every week. A new model, a new parameter, an error format that changes overnight. Who fixes your thousand lines? You do. Forever. With a framework, somebody else fixes it, and everybody gets the fix. And here’s what’s in this one now. RubyLLM 2.2 Tools ask a person. Window or aisle? The call pauses, no process sits there waiting, and when they answer, execute runs again from the top. Ask first, then act. class BookFlight < RubyLLM::Tool description "Books a flight" parameter :destination, description: "The city to fly to" def execute destination: seat = ask "Window or aisle?", choices: %w window aisle or return "No seat chosen." Flights.book destination, seat: end end And from any process, three verbs. Answer, approve, decline. question = chat.pending questions.first question.answer "window" question.approve remember: true question.decline "I'll choose at the gate" chat.complete Let me show you the rest, one at a time. A chat survives a deploy. One process runs it at a time, so a double click never runs a tool twice. Deploy in the middle of a reply? Chat.interrupted finds it, and the generated ResumeInterruptedChatsJob picks it up as soon as the server boots. A tool call that had started never runs again: the model hears it may have partly run, and decides. Code in a sandbox, on your own servers. That’s thanks to mxc https://crmne.github.io/mxc/ , my new gem. It’s Microsoft’s MXC, from Ruby, and improved. The operating system’s own isolation. The files it makes come back as attachments. chat = RubyLLM.chat.with code execution python: %w pandas matplotlib chat.ask "Plot monthly revenue from this export", with: "revenue.csv" File stores. That 40-page PDF doesn’t travel with every question anymore. The model reads it with tools, like a coding agent does. chat = RubyLLM.chat.with file store Context budgets. One giant tool result can’t break the chat. chat.with context budget per result: 0.25, per request: 0.7 Skills. Your workflows, in folders, loaded only when a task needs them. In the open format every coding agent speaks. Tests, no provider. Script what the model says, and run your real agents and pages. stub replies tool call :weather, city: "Berlin" , "It is sunny.", cost: 0.01 A generator for every building block. Agents, tools, schemas, MCP servers, skills, sandboxes, evaluations, judges. And the chat UI, with a model switcher on every message. Twenty-three providers. 518 built in, every one checked against each provider’s own docs. One API. There’s a lot more in What’s New in 2.2 https://rubyllm.com/whats-new-in-2-2/ . And where do all these features come from? I didn’t sit in a room and invent them. Rails Came from Basecamp. RubyLLM Comes from Chat with Work. DHH didn’t design a web framework. He built Basecamp and pulled out what kept working. Same for me. Everything new in 2.2 earned its place in a real product. And I think what we’ve built is amazing. Chat with Work https://chatwithwork.com . AI bots that work for you. They work. You decide. A bot starts on an event or a time, and works across your tools. What needs you comes to one Decisions dashboard. Changes ask first, unless you let them run. And every one of those chats is rows in my database. My tables, my backups, my queries. The application owns the conversation. This Is Our Time. Python had the training. JavaScript had the browser. But building AI products? Products people pay for, with approvals and billing and a database you own? That’s what Rails was made for. And now we have the framework for the AI part too. RubyLLM 2.2. Out today. bundle update ruby llm bin/rails generate ruby llm:upgrade bin/rails db:migrate Still on 1.16? One release at a time. The upgrade guide https://rubyllm.com/upgrading/ has every step. Try it, and send me what you find. Frameworks matter.