Building an AI agent in Rails with background jobs
The agent loop is a job that reads state, acts once, saves state, and re-enqueues itself.
An AI agent sounds like new infrastructure. In Rails, it is a background job that loops: read state, ask the model, maybe run a tool, save state, re-enqueue. ActiveRecord is the agent memory, and that is a feature.
class AgentStepJob < ApplicationJob
MAX_STEPS = 8
def perform(run_id)
run = AgentRun.find(run_id)
return if run.finished?
return run.fail!("step budget exceeded") if run.steps.count >= MAX_STEPS
decision = LlmClient.next_step(run.transcript, tools: run.tool_schemas)
case decision.type
when :tool_call
result = run.dispatch_tool(decision) # a scoped service object
run.record_step!(decision, result)
AgentStepJob.perform_later(run.id)
when :final_answer
run.record_step!(decision, nil)
run.finish!(decision.content)
end
end
end
Why one step per job instead of a loop inside one job? Because everything Rails gives a job, the agent now inherits per step:
- A crash mid run resumes from the database, not from zero.
- Retries apply to the step that failed, not the whole run.
- Every step is a row: you can read the full history of any run in the console, which is how real debugging happens.
- The step budget is a hard ceiling in code. Runaway loops die at step eight, not at invoice time.
The AgentRun model holds the transcript, the allowed tools, the actor it acts for, and the status. Nothing about this requires an orchestration platform. It requires the discipline you already apply to money-touching jobs: idempotency keys on tool calls, and state transitions through one model.
We have shipped this shape inside client codebases as small as two engineers. If you want it inside yours with a senior owner attached, that is the Forward Deployed AI Engineer engagement: $9,500 a month, production by day 90.