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.
Durable AI agents in Rails: Temporal Ruby SDK, Solid Queue and Sidekiq (2026)
Durable execution
By Cadenya
Durable AI agents in Rails come in two shapes: a durable execution engine like the Temporal Ruby SDK, or Active Job on Solid Queue, GoodJob, or Sidekiq with Rails 8.1 job continuations (ActiveJob::Continuable). Both let a long-running LLM agent turn survive a deploy, and both leave the agent loop for you to write.
So pick Temporal when you want signals for human approval and you’re fine running a Temporal Service and a worker. Pick Active Job continuations when you’d rather keep the queue you already have and split each agent turn into steps. And if you want your Rails app to stay a stateless web tier behind the load balancer with nothing to host, Cadenya is a hosted agent loop with a Ruby SDK, and 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.
Durable AI agents in Rails side by side
| Temporal Ruby SDK | Sidekiq | Solid Queue | GoodJob | Cadenya | |
|---|---|---|---|---|---|
| What it is | Durable execution: workflows, activities, signals | Redis-backed background jobs | Database-backed Active Job backend from the Rails team | Postgres-based Active Job backend | A hosted agent loop with durable state and approvals |
| What you run | A Temporal Service (or Temporal Cloud) plus worker processes | Redis 7.0 or later (or Valkey, Dragonfly) plus Sidekiq processes | Your SQL database (MySQL, PostgreSQL, or SQLite) plus bin/jobs | Postgres 10 or later plus GoodJob workers | Nothing. Your Rails app calls an HTTP API and receives webhooks |
| Ruby and Rails | Ruby 3.3 or later. Works in any Ruby app | Ruby 3.2+, Rails 7.0+ | Ruby 3.2+, Rails 7.1+ | Ruby 3.0+, Rails 6.1+ | The cadenya gem, called from your Rails app |
| Rails 8.1 job continuations | Its own model: workflows and activities | stopping? since 8.0.5 | stopping? in its adapter | GoodJob::Adapter#stopping | Not needed: Cadenya keeps the loop’s state, with no steps to split |
| Multi-step work | Workflows that call activities | Iterable jobs; Batches in Pro and Enterprise | Batches, concurrency controls, recurring tasks | Batches, cron, concurrency and throttling controls | The agent loop: model calls, tool runs, and approvals |
| License and price | MIT. Temporal Cloud: no base fee, $50 per million actions | LGPL-3.0. Pro $99 a month or $995 a year; Enterprise from $269 a month per 100 threads | MIT | MIT | Apache-2.0 gem. Free for 1,000 loops a month, $49 a month for 50,000, then $0.0015 a loop |
| Latest release | 1.9.0, September 14, 2026 | 8.1.7, August 17, 2026 | 1.7.0, August 21, 2026 | 4.22.0, October 9, 2026 | 1.9.0 |
Temporal Ruby SDK general availability and Rails integration
Temporal’s changelog reads: “the fully-featured Temporal Ruby SDK is generally available.” The temporalio gem is at 1.9.0, MIT licensed, and its gemspec asks for Ruby 3.3 or later.
A Temporal workflow waits for a person with a signal. The handler sets an instance variable, and wait_condition blocks until it’s true. This is the signal handler from Temporal’s Ruby message-passing docs, inside a workflow:
class RefundApprovalWorkflow < Temporalio::Workflow::Definition
def execute(refund_id)
Temporalio::Workflow.wait_condition { @approved_for_release }
"refund #{refund_id} approved by #{@approver_name}"
end
workflow_signal
def approve(input)
# A signal handler mutates the workflow state but cannot return a value.
@approved_for_release = true
@approver_name = input['name']
end
end
The model call can’t live in that workflow. Temporal workflows “must be deterministic,” and the README lists what they can’t do: “Perform IO (network, disk, stdio, etc),” read the system clock, or make random calls. Every LLM request and tool call goes in an activity, run with Temporalio::Workflow.execute_activity. The SDK enforces this at runtime: workflows run on “a custom, deterministic Fiber::Scheduler,” and a TracePoint catches illegal calls like sleep, Time.now, and Thread.new.
The Rails integration has three rules from the README:
- Don’t pass ActiveRecord models into workflows. “It is not recommended to try to reuse them as Temporal models.” Use plain data or ActiveModel objects and translate.
- Eager load workflow code. Lazy loading in development raises an illegal call error, so set
config.eager_load = trueorrequirewhat a workflow uses (“this only affects non-production environments”). - Run the worker as its own process. The official
rails_appsample starts it withbin/rails runner script/temporal_worker.rb, next tobin/rails server.
Temporal Cloud has “no base monthly fee,” starts at “$50 per million actions,” and gives new accounts $150 in credits for 90 days. The Temporal cost breakdown compares it with self-hosting.
Rails 8.1 ActiveJob::Continuable job continuations
Rails 8.1.0 shipped on October 22, 2025 with job continuations. The source docs say they “allow long-running jobs to make progress across application restarts.” You include ActiveJob::Continuable and split perform into steps:
class ProcessImportJob < ApplicationJob
include ActiveJob::Continuable
def perform(import_id)
# This always runs, even if the job is resumed.
@import = Import.find(import_id)
step :validate do
@import.validate!
end
step(:process_records) do |step|
@import.records.find_each(start: step.cursor) do |record|
record.process
step.advance! from: record.id
end
end
end
end
Here’s how the step, the cursor, and SIGTERM fit together. On resume, Active Job skips the steps that finished, and a step in progress picks up “with the last recorded cursor.” Interruption happens only at a checkpoint, where the job calls queue_adapter.stopping?. Each new step, set!, advance!, and checkpoint! is a checkpoint. Between checkpoints, a job keeps running “until the next checkpoint, or when the process is stopped.” Then Active Job retries it “with the progress serialized in the job data under the continuation key.”
For an agent, that means each model call and each tool call should be its own step or cursor advance. Code outside a step runs “on each job run,” so a model call there runs again on every resume. A step that can’t checkpoint in time takes isolated: true, which saves progress before the step starts.
What about errors? A job that “raises an error after it has made progress” retries automatically. But if a job raises and Active Job doesn’t retry it, the error goes back to the queue backend and that execution’s progress is gone.
All three Rails queues support it. Sidekiq added stopping? to its Active Job adapter in 8.0.5. Solid Queue’s adapter sets the flag on on_worker_stop. GoodJob added GoodJob::Adapter#stopping “to support Active Job continuations,” and later let continuation jobs be paused. (Rails 8.1 also deprecates its built-in sidekiq adapter, which is set for removal in Rails 8.2. Sidekiq ships its own.)
Solid Queue vs GoodJob vs Sidekiq for Postgres background jobs
The choice comes down to the database you already run.
Solid Queue works with “MySQL, PostgreSQL, or SQLite” and is fastest on “MySQL 8+, MariaDB 10.6+, or PostgreSQL 9.5+,” which support FOR UPDATE SKIP LOCKED. For Solid Queue Rails 7.2 compatibility, the 1.7.0 gem requires Active Job, Active Record, and Railties 7.1 or later, and Ruby 3.2. So Rails 7.2 works. It covers delayed jobs, concurrency controls, recurring tasks, pausing queues, priorities, and batches. For a UI, the README points to Mission Control Jobs, “a Rails-based frontend to Active Job adapters” that supports Resque and Solid Queue.
GoodJob is “a multithreaded, Postgres-based, Active Job backend.” It uses advisory locks for run-once safety and “LISTEN/NOTIFY to reduce queuing latency,” and it ships cron, batches, concurrency controls, and a web dashboard. It needs Postgres 10, Rails 6.1, and Ruby 3.0 or later.
Sidekiq is the one that isn’t Postgres. Sidekiq 8 needs Ruby 3.2, Rails 7.0, and Redis 7.0 or later since 8.0.5 (8.0.0 asked for 7.2). Its iterable jobs make “deployments with long-running jobs safer and cleaner.” The open-source gem has a gap that matters for agents: “If a Sidekiq process crashes while processing a job, that job is lost.” Sidekiq Pro’s super_fetch uses Redis’ LMOVE command, “which keeps jobs in Redis,” for $99 a month or $995 a year.
Durable execution Ruby gems: acidic_job, ChronoForge, Restate and Hatchet
Several gems add workflows on top of Active Job, or bring a durable execution engine to Ruby.
acidic_job adds “durable execution workflows for Active Job.” You call execute_workflow(unique_by: ...) with a list of steps, and an AcidicJob::Execution record tracks where it is. The unique_by key keeps the workflow idempotent, so “your step methods only need to be idempotent on failure, not on success.” A ctx hash persists data across steps, and halt_workflow! stops the job until you enqueue it again (that’s your hook for an approval).
ChronoForge is “a gem over your existing database and ActiveJob backend: no DSL to learn, no separate server or daemon to run.” A workflow is an Active Job class that prepends ChronoForge::Executor, and its primitives are durably_execute, wait, wait_until, and durably_repeat. It works with any Active Job backend on Rails 7.1 or later.
Restate has a Ruby SDK: the restate-sdk gem, MIT, for Ruby 3.1 or later. Services subclass Restate::Service, and Restate.run_sync journals a side effect. Restate vs Temporal compares the two engines.
Hatchet has an official Ruby SDK too: hatchet-sdk, MIT, for Ruby 3.1 or later. Its changelog lists “durable tasks, durable events, and durable sleep” and a Hatchet::DurableContext. Its pay-as-you-go plan includes 1 million runs a month, and the engine is “100% open-source and MIT licensed.”
Long-running LLM agent turns that survive a deploy
Every option above makes the job or workflow durable. None of them is the agent.
You still write the loop: call the model, parse tool calls, run each tool, append results, and call the model again. You write context compaction when the conversation outgrows the window. You write streaming from the worker to the browser, through Action Cable or a polling endpoint, because the worker isn’t the process holding the request. And you build the approval screen and wire it to a signal or a re-enqueued job. Durable execution for AI agents and human-in-the-loop approvals cover those parts.
With Cadenya, the loop runs on Cadenya, and your Rails app receives events. This controller is the Ruby handler from Cadenya’s webhooks guide, moved into Rails:
class CadenyaWebhooksController < ActionController::API
def create
# Reads CADENYA_API_KEY and CADENYA_WEBHOOK_SECRET from the environment.
client = Cadenya::Client.new
headers = {
"webhook-id" => request.headers["webhook-id"],
"webhook-timestamp" => request.headers["webhook-timestamp"],
"webhook-signature" => request.headers["webhook-signature"]
}
event = client.unwrap_webhook(request.raw_post, headers)
Rails.logger.info("#{event.type} #{event.data.objective.id}")
head :ok
rescue Cadenya::WebhookVerificationError
head :unauthorized
end
end
Pass the raw body (request.raw_post), since parsing and re-serializing the JSON changes the signed bytes. And treat webhook-id as an idempotency key with a unique index.
What does Cadenya add to Temporal Ruby, Sidekiq and Solid Queue?
Keep Sidekiq or Solid Queue for your mailers and imports. Cadenya runs the agent itself: the loop, durable state, and approvals, with a Ruby gem and nothing to host. Here’s what that covers:
- Nothing else to run. No Temporal Service, no worker process, no Redis, and no extra queue database for the agent. The loop runs on Cadenya and your Rails app stays a stateless web tier, so a deploy or a restart never touches a running objective.
- No commercial license to buy and no engine to self-host. There’s no Pro tier to buy for reliable fetching and no server to keep patched. You install the Apache-2.0
cadenyagem and call an HTTP API. - No determinism rules and no old code to keep alive. An objective keeps its variation’s configuration snapshot even when you edit the variation. So there’s no workflow code to patch or version, and no rule against calling the network from the wrong place.
- Durable state you don’t split into steps. Cadenya calls the model, runs the tools, streams each reply, and compacts the context window when a conversation gets long. On Temporal or Active Job continuations, that’s the code you’d split into activities or steps so a deploy can resume it.
- Approvals that wait through your deploys. Mark a tool as needing approval and Cadenya parks the loop until it gets an approval or a denial, and several tools can wait at once. Because the loop runs on Cadenya, a Rails deploy in the meantime leaves the waiting objective alone, and the decision can come from your backend or a Slack handler.
- A durable event log for every objective. Every objective keeps a durable event log you can list after the run. The conversation event stream honors
Last-Event-ID, so a dropped connection picks up where it left off, with no Action Cable glue. - 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, and there are no thread counts or seats.
- SDKs at 1.9.0 in four languages. Ruby, TypeScript, Python, and Go, all at 1.9.0 under Apache-2.0. The TypeScript SDK has shipped 113 versions since April 2026.
How objectives work walks through an agent turn and its approvals, 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.