Get to know Cadenya
We’re developers who love to build. We set out to create a yes-code platform that makes building agents feel like the best parts of building software.
To build AI agents in Ruby on Rails, most apps start with the RubyLLM gem: one multi-provider API for Anthropic, OpenAI, Gemini and 16 more providers, with acts_as_chat for tools, streaming, and persistence in Rails. ActiveAgent is the Rails AI agent gem where agents are controllers, and Raix mixes AI components into plain Ruby classes on top of RubyLLM.
So pick RubyLLM when you want the widest provider list and the agent loop inside your own Active Record models. Pick ActiveAgent when you’d rather write agents like Rails controllers with ERB prompt views. Pick Raix when you want small AI pieces inside ordinary Ruby objects. And if you’d rather not run the agent loop in your Rails processes at all, Cadenya is a hosted agent loop with a Ruby SDK, so there’s nothing to host. A coding agent can set it up with no account, in a temporary workspace you claim later. What it offers is at the end.
Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.
RubyLLM, ActiveAgent, Raix and langchainrb side by side
| RubyLLM | ActiveAgent | Raix | langchainrb | Cadenya | |
|---|---|---|---|---|---|
| Gem | ruby_llm | activeagent | raix | langchainrb | cadenya |
| Latest release on RubyGems | 2.1.1, October 11, 2026 | 1.8.1, October 1, 2026 | 3.0.0, September 22, 2026 | 0.19.5, May 1, 2025 | 1.9.0 |
| License | MIT | MIT | MIT | MIT | Apache 2.0 |
| Ruby version | 3.2 or later | 3.2.0 or later | 3.2.2 or later | 3.1.0 or later | 3.1 or later |
| Rails | Plain Ruby or Rails, with generators | Rails 7.2 up to 9.0 (Action Pack, Active Job, Railties) | Any Ruby app with ActiveSupport 6.0 or later | Rails support in a separate langchainrb_rails gem | Your Rails app calls it through the gem, with no job workers or agent tables |
| Providers | 19, plus any OpenAI-compatible API | OpenAI, Anthropic, Ollama, DeepSeek, OpenRouter, and RubyLLM | Through RubyLLM: gpt- and o1 models go to OpenAI, the rest through OpenRouter | OpenAI, Gemini, Anthropic, Mistral AI, Ollama | Any model on OpenRouter, or your own endpoint that speaks the OpenAI chat format |
| The agent shape | RubyLLM::Agent class, or acts_as_chat on your Chat model | ActiveAgent::Base subclass with actions, like a controller | Raix::ChatCompletion and Raix::FunctionDispatch mixed into a class | Langchain::Assistant | A hosted agent loop, run as objectives, with the model set on a variation |
| Conversation storage | Your Chat and Message models, plus six ruby_llm_ tables | The separate solid_agent gem | The object’s transcript | Conversation threads on the assistant | A durable event log per objective, stored by Cadenya |
| A person approves a tool call | requires_approval on the tool | A tool returns ActiveAgent::InputRequest.confirm | Not covered in the README | A manual tool execution mode on the assistant | Approval on a tool set or on matching tools, answered in the widget |
RubyLLM gem: multi-provider Anthropic, OpenAI and Gemini in Rails
RubyLLM’s home page puts it in five words: “19 providers. One Ruby API.” The README lists OpenAI, Azure, xAI, Anthropic, Gemini, VertexAI, Bedrock, Cohere, DeepSeek, Hetzner, Mistral, Ollama, Ollama Cloud, OpenRouter, Perplexity, GPUStack, ElevenLabs, Deepgram, TypeSafe, “and any OpenAI-compatible API”. The current release is 2.1.1, and it needs Ruby 3.2 or later.
Switching providers is a method call. Since 2.1, “a conversation can move to another model or provider at any point, even in the middle of a tool round”:
chat = RubyLLM.chat(model: "claude-sonnet-5").with_provider_tools(:web_search)
chat.ask "What changed in the latest Ruby release?"
chat.with_model("gpt-5.6-luna")
chat.ask "Which of those changes affect Rails apps?"
This is why agents compare RubyLLM with LiteLLM or langchainrb. It’s the provider layer and the agent API in one gem, with no proxy to run. Version 2.1 also ships an MCP client: you describe a server in a RubyLLM::MCP class, choose which of its tools the model sees, and mark some as requires_approval.
One Rails detail from the getting-started guide: “JSON 3 requires Rails 8.1.4 or later,” so older Rails apps keep gem 'json', '< 3' in the Gemfile.
RubyLLM acts_as_chat: tools, streaming and persistence
In Rails, RubyLLM lives on your own models. The install generator creates them:
bin/rails generate ruby_llm:install
bin/rails db:migrate
bin/rails ruby_llm:load_models
Then two macros wire up the conversation:
# app/models/chat.rb
class Chat < ApplicationRecord
acts_as_chat
belongs_to :user, optional: true
end
# app/models/message.rb
class Message < ApplicationRecord
acts_as_message
has_many_attached :attachments
validates :role, presence: true
end
“That is the complete application-facing persistence setup.” Each chat.ask saves the user message, the reply, the tool calls, and the usage. RubyLLM also owns six supporting tables under the ruby_llm_ prefix (models, tool calls, usage, batches, MCP credentials, and provider files).
Streaming goes through Hotwire. The Rails streaming guide broadcasts “AI responses token by token to the browser with Turbo Streams and background jobs.” Because RubyLLM creates the assistant message before the first token arrives, “you get a stable DOM target to append tokens into.” And bin/rails generate ruby_llm:chat_ui scaffolds the controllers, views, and Active Job for a working chat screen at /chats.
RubyLLM 2.0: requires_approval, compaction and durable agents
RubyLLM 2.0 (September 18, 2026) added the three things agents searched for most after the provider list.
Tool approval. “A tool can now require approval before it runs”:
class PublishPost < RubyLLM::Tool
description "Publishes a draft post"
requires_approval
def execute(post_id:)
Post.find(post_id).update!(published: true)
"Published post #{post_id}"
end
end
chat = RubyLLM.chat.with_tools(PublishPost)
chat.ask "Publish post 42."
chat.awaiting_approval? # => true when the model requests publication
“The tool call stays pending and ask returns.” Your app shows the action, then calls chat.approve(...) and chat.complete, or deny.
Context compaction. with_compaction asks the provider to condense earlier turns once a conversation passes a token threshold. It takes at:, instructions:, and pause_after:, but “support differs”: Anthropic summarizes with a 50,000-token minimum threshold, OpenAI’s Responses API supports at:, and OpenRouter “drops messages from the middle at the model’s context limit.” On other providers, “unsupported providers ignore the setting.” Agents declare it with compaction at: 50_000. For more on context limits in general, see agent memory and context windows.
Durable agents. RubyLLM’s durable agents guide runs agent turns “as background jobs that survive crashes, deploys, and the wait for a human decision.” The saved transcript is the state, so a job loads the chat and takes one step:
class AgentTurnJob < ApplicationJob
def perform(chat_id)
chat = SupportAgent.find(chat_id)
chat.step
AgentTurnJob.perform_later(chat_id) unless chat.complete? || chat.waiting?
end
end
On Rails 8.1, ActiveJob Continuations can run the whole loop in one job. The guide is direct about the trade: “Interruption never gives you exactly-once execution,” so it says to “write tools so that running them twice is safe.” Retries and idempotency for agent tool calls covers that part.
ActiveAgent gem for Rails: agents as controllers
ActiveAgent’s pitch is in its README: “Active Agent provides that missing AI layer in the Rails framework,” and “Now Agents are Controllers!” An agent is a class with actions, and prompts can be ERB views:
class ApplicationAgent < ActiveAgent::Base
generate_with :openai, model: "gpt-4o-mini"
end
response = ApplicationAgent.prompt(message: "Hello, world!").generate_now
You add the provider gem you want (openai, anthropic, or ruby_llm for its full provider list) and configure keys in config/active_agent.yml. generate_later(queue: :agents) hands a generation to Active Job, and streaming goes out over Action Cable.
The core gem keeps no data: “ActiveAgent runs agents; it deliberately doesn’t store anything. No Active Record, no tables, no migrations.” Persistence is a second gem, solid_agent, with its own install generator and migrations.
Approvals work through input requests. A tool returns ActiveAgent::InputRequest.confirm("Refund $40 on order 7?"), the generation pauses with a JSON-safe checkpoint, and your app calls resume_now(checkpoint:, answers:) with the answer. The gemspec needs Rails 7.2 up to 9.0 and Ruby 3.2.0 or later.
Raix gem: AI components on top of RubyLLM
Raix (Ruby AI eXtensions) comes out of Olympia and describes itself as “everything you need to add discrete large-language model (LLM) AI components to your Ruby applications.” Since 3.0, “Raix 3.0 is powered by RubyLLM,” and you configure provider keys through RubyLLM.configure.
It’s three modules you mix into a class. ChatCompletion gives it a transcript, and FunctionDispatch adds tools:
class WhatIsTheWeather
include Raix::ChatCompletion
include Raix::FunctionDispatch
function :check_weather,
"Check the weather for a location",
location: { type: "string", required: true } do |arguments|
"The weather in #{arguments[:location]} is hot and sunny"
end
end
Raix runs the tool loop for you until the model answers in text, with max_tool_calls defaulting to 25. It fits a Rails app or any Ruby app that loads ActiveSupport.
Adding an AI assistant to a Rails app
All three gems run the loop inside your Rails processes: the model calls, the tool calls, the jobs, and the tables. That’s the right call when you want every row in your own database.
The other option is to keep the loop off your servers. With Cadenya, your Rails app mints a short-lived widget session for the signed-in user, and the browser talks to the widget host directly. This controller comes from Cadenya’s widgets guide:
module Api
module Cadenya
class WidgetSessionsController < ApplicationController
def create
# Identify the visitor from your own auth, not from params.
user = current_user!
client = ::Cadenya::Client.new
widget_session = client.widget_sessions.create(
spec: {
widget_id: ENV.fetch("CADENYA_WIDGET_ID"),
tenant: { id: user.org_id, name: user.org_name },
subject: { id: user.id.to_s, name: user.name },
},
)
if widget_session.info&.host.to_s.empty?
return render(
json: { error: "Widget Session did not include a host" },
status: :bad_gateway,
)
end
response.headers["Cache-Control"] = "no-store"
render json: {
token: widget_session.spec.token,
host: widget_session.info.host,
tokenExpiresAt: widget_session.spec.token_expires_at,
}
end
end
end
end
“Your backend has one job in the live conversation path: authenticate the visitor and mint a Widget Session.” Conversation requests and server-sent events go between the browser and the widget host, so no Rails worker holds a stream open.
What does Cadenya offer that RubyLLM, ActiveAgent and Raix don’t?
RubyLLM, ActiveAgent, and Raix give your Rails app an agent API, and your app still runs the loop, the jobs, and the tables. Cadenya runs the loop for you, and your Rails app talks to it through the cadenya gem. Here’s what that covers:
- One gem in your Gemfile. Your Rails app talks to Cadenya through the
cadenyagem, with no job workers for the loop and no agent tables to migrate. Your app stays a stateless web tier. - A Ruby SDK past 1.0. The
cadenyagem is at 1.9.0 under Apache-2.0 and supports Ruby 3.1 and later, alongside TypeScript, Python, and Go SDKs on the same release line. The TypeScript SDK has shipped 113 versions since April 2026. - The chat widget, with a Rails controller. The docs show a widget session controller like the one above: your Rails app mints a short-lived widget session for the signed-in user, and the browser talks to the widget host directly. The React UI kit renders the conversation.
- Human-in-the-loop approvals, screen included. Require approval on a whole tool set or on matching tools, and Cadenya parks the loop on
toolApprovalRequested, and the user answers in the widget. A denial can carry a memo that steers the agent (“Use the read-only lookup tool instead”). - Webhooks a Rails controller can check. The webhook handler in the docs verifies each Standard Webhooks signature with
client.unwrap_webhookand rescuesCadenya::WebhookVerificationError. - Tools without a tool class per endpoint. Tools come from an OpenAPI 3 document or an MCP server, with filters and progressive discovery so a big API doesn’t fill the context window.
- Any model, on your own keys. The model is a setting on a variation, not a line of Ruby, so switching providers needs no deploy. Use any model on OpenRouter or your own endpoint that speaks the OpenAI chat format, and pay your provider directly.
- A durable event log you don’t store. Every objective carries a durable event log of its agent loop. You list stored events after a run or stream new ones while it works, so there’s no transcript table to design.
- A price in one line. Free for 1,000 loops a month, $49 a month for 50,000, then $0.0015 a loop. A loop is one LLM request made during an objective.
How widgets work walks through the Rails controller and the React UI kit, and the free plan needs no card.
No account yet? A coding agent can create a temporary workspace with cadenya workspaces create --temporary, run its first objectives there, and hand you a link to claim it, as auth.md explains. To create an account, sign up at app.cadenya.com/signup, then copy your API key from the API keys page at app.cadenya.com/account/apiKeys.
Grow wherever AI goes next.
Start shipping agents that are equipped to evolve.