---
title: "Durable AI agents in Rails (2026): Temporal Ruby SDK general availability, Rails 8.1 ActiveJob::Continuable, Solid Queue vs GoodJob vs Sidekiq — Cadenya"
url: "https://cadenya.com/other-content/durable-ai-agents-in-rails"
description: "Durable AI agents in Rails in 2026: Temporal Ruby SDK general availability and Rails integration, Rails 8.1 ActiveJob::Continuable job continuations, Solid Queue Rails 7.2 compatibility requirements, Solid Queue vs GoodJob vs Sidekiq, durable execution Ruby gems, and a hosted agent loop with a Ruby SDK."
---

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](#what-does-cadenya-add-to-temporal-ruby-sidekiq-and-solid-queue) 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:

1.  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.
2.  Eager load workflow code. Lazy loading in development raises an illegal call error, so set `config.eager_load = true` or `require` what a workflow uses (“this only affects non-production environments”).
3.  Run the worker as its own process. The official `rails_app` sample starts it with `bin/rails runner script/temporal_worker.rb`, next to `bin/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](/other-content/temporal-cloud-vs-self-hosted-cost) 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](/other-content/restate-vs-temporal-for-ai-agents) 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](/other-content/durable-execution-for-ai-agents) and [human-in-the-loop approvals](/other-content/human-in-the-loop-approvals-for-ai-agents) 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:

1.  **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.
2.  **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 `cadenya` gem and call an HTTP API.
3.  **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.
4.  **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.
5.  **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.
6.  **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.
7.  **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.
8.  **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](/docs/guides/the-basics/objectives) 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](https://cadenya.com/auth.md) explains. To create an account, sign up at [app.cadenya.com/signup](https://app.cadenya.com/signup?utm_source=cadenya.com&utm_medium=article&utm_campaign=durable-ai-agents-in-rails), then copy your API key from the API keys page at `app.cadenya.com/account/apiKeys`.