Durable Agents
Keep an agent working across jobs, deploys, and human decisions, and find the guide for each piece.
After reading this guide, you will know:
- When an agent needs to be durable, and when a plain chat is enough.
- How a durable agent waits for something outside it.
- Which guide covers each piece: jobs, run claims, approvals, input requests, tasks, queued messages, files, and code execution.
When You Need Durable Agents
Most agents answer inside one request. Call ask, show the answer, and you are done. A Chat or an Agent is all you need.
An agent becomes durable when a turn outlives the process that started it:
- It runs longer than a web request, so a background job runs it.
- It waits for a person to approve a tool call or answer a question, which can take hours.
- A tool hands work to something slower, such as a deploy or another agent.
- People send more messages while it is still working.
- It keeps files or runs code across many turns.
- It must carry on after a deploy or a crash.
Durable agents use Rails persistence. The saved transcript records what has happened, so any process can load the chat through its agent class and continue:
class CompleteJob < ApplicationJob
def perform(chat_id)
chat = SupportAgent.find(chat_id)
chat.complete
CompleteJob.set(wait: 1.minute).perform_later(chat_id) if chat.running?
end
end
When another process still holds the chat, complete returns without doing anything, so the job tries again later. See Resuming a Crashed Run.
Waiting for Something Outside
A durable agent never blocks a process while it waits. When a tool call needs a decision, an answer, or work that runs elsewhere, the call pauses, complete returns, and the chat is waiting?:
chat = SupportAgent.find(chat_id)
chat.complete
chat.waiting? # => true
chat.awaiting_approval? # a tool call needs approval
chat.awaiting_input? # a tool or MCP server asked a question
chat.awaiting_tasks? # a tool call runs as a task
Record the approval or the answer from your controller, then enqueue the job again. The next complete picks up where the last one stopped.
The Pieces
Each piece has its own guide. Read the ones your agent needs.
| Piece | What it does | Guide |
|---|---|---|
| Background jobs | Run one turn per job, survive deploys with ActiveJob Continuations, and stop a run from anywhere. | Agents in Background Jobs |
| Run claims | Let only one process run a chat or a tool call at a time, and resume a run after a crash. | One Process at a Time, Long-Running Tools |
| Approvals | Pause a tool call until a person approves or denies it. | Requiring Approval, Approval in Rails |
| Input requests | Let a tool or an MCP server ask the user a question in the middle of a call. | Asking the User, MCP Input Requests |
| Tasks | Pause a tool call while slow work runs elsewhere, then resume it. | Pausing a Tool Call, MCP Tasks |
| Queued messages | Accept new messages while a job runs the chat, and decide when they join. | Queueing Messages, Queued Messages in Rails |
| File stores | Keep attachments and long tool results as files the model reads when it needs them. | Chat Files |
| Sandboxes | Decide what code from a model can read, write, and reach. | Sandboxes |
| Code execution | Let the model run code in a sandbox and hand back the files it makes. | Code Execution |
Running Twice Is Possible
A durable agent runs each tool call at least once, not exactly once. A job that dies after a tool runs, but before its result is saved, runs that tool again. Write tools so that running them twice is safe. See At-Least-Once, Not Exactly-Once.
Next Steps
- Agents in Background Jobs - Start here to run your agent in jobs.
- Agentic Workflows - The loop methods a job drives.
- Streaming with Hotwire/Turbo - Show a background run as it happens.