Elixir and Oban: Building Reliable AI Agent Workflows

The first version of an AI feature often fits inside one function: load some data, call a model, save the answer. Then the feature grows. It needs to read several documents, call another service, wait for approval, and notify the user. An API times out. A deploy interrupts the work. Someone clicks the button twice. Now you need a way to track what finished, retry what failed, and avoid doing the same work again.
This is where Oban fits into an Elixir application. It gives background work a stored record and a managed execution cycle. For AI features, that means you can build around slow calls and partial failures without writing your own job system.
The useful question is where to put the job boundaries and what should happen when a step runs twice.
What Oban handles in an AI workflow
In a PostgreSQL-backed setup, Oban stores jobs in your database. Each job points to a worker module and carries arguments such as a document ID or a workflow ID.
When a queue has capacity, Oban runs the worker. It records the outcome and schedules another attempt when a failure is retryable and attempts remain.
Your worker still owns the actual task: calling the model, validating its response, saving the result, or updating another system. Oban manages the execution around it.
This fits naturally into an existing Elixir application. Workers can call your context modules, use your Ecto repo, and share your application configuration. You do not need a separate message broker for this setup.
The Oban documentation covers the core model and setup.
Why a supervised process is only part of the answer
A supervisor can restart a process. Your application still needs to know which business task was running, what it already changed, and what should happen next.
For example, restarting a process does not tell you whether a model response was saved before the crash. It also does not tell you whether an external service accepted a request before the connection dropped.
Oban gives the task a record that survives the process. Your application must save the results needed to recover it.
That is the useful division of responsibility: supervision helps manage running processes; stored jobs and application data help recover unfinished work.
Split the workflow where recovery matters
Consider a feature that reads an uploaded document and prepares a response for review.
| Step | Work to do | Result to save |
|---|---|---|
| Extract | Read the document and extract its text | Extracted text or a reference to it |
| Generate | Send the relevant content to the model | Draft response and model details |
| Review | Ask a person to approve or edit the draft | Decision, reviewer, and approved version |
| Deliver | Send the approved response | Delivery status and provider reference |
You could put the whole process in one worker. But if delivery fails, retrying that worker may repeat extraction and generation too.
Separate jobs let you retry a failed step using the saved output of earlier steps. That can avoid extra model calls and make failures easier to investigate.
Do not make every small function a job. A useful boundary is a step that is slow, expensive, calls an external service, or needs its own retry policy.
There is one detail worth knowing: in a standard Oban worker, returning {:ok, result} marks the job as successful, but does not persist that result for the next step. Save it explicitly. The worker documentation explains return values and retry behavior.
Save the result and schedule the next step together
Suppose extraction finishes. You save the text, then enqueue the job that calls the model.
If the application crashes between those two operations, the text exists but nothing schedules the next step.
Use a database transaction to save the result and insert the next job together. With the same database and repo, Ecto.Multi lets you group those changes so they either both succeed or both roll back.
This is a useful pattern for the first step too: create the workflow record and enqueue its first job in one transaction.
Keep external API calls outside long-running database transactions. A database rollback cannot undo a request that another service has already accepted.
Make retries safe before adding more of them
A retry is helpful when the model provider is temporarily unavailable. It is less helpful when the request is invalid or the account has no usable credentials.
Give each failure a clear response:
- Temporary outage: retry with a delay and a limited number of attempts.
- Rate limit: respect the provider’s retry guidance and reduce request pressure.
- Invalid input: stop and record what needs fixing.
- Unexpected model output: validate it, then follow an explicit retry or review policy.
Set timeouts for external calls and a suitable execution timeout for the worker. Avoid a job waiting indefinitely for a service that will never respond.
Also assume that a job may execute more than once. A provider might accept a request just before your worker crashes, leaving your application unsure whether it succeeded.
For external actions, use a stable idempotency key when the provider supports one. Keep the same key across attempts. When the outcome is uncertain, use the provider’s lookup or reconciliation API if available.
Oban’s unique jobs feature can limit duplicate job insertion under configured rules. It does not make an email, payment, or model request happen exactly once. A local “already done” check helps, but does not close every gap between an external action and saving its result.
Use queues to control pressure on your services
An AI request may spend most of its time waiting on a remote API. Even so, running hundreds of requests at once can hit provider limits and raise costs quickly.
Separate work with different needs. Interactive requests, bulk document processing, and notifications are reasonable candidates for different queues.
Choose limits based on latency, provider quotas, database load, and budget.
With local limits, adding application nodes can increase total concurrency. If your provider quota is shared across the whole deployment, you need a shared limit too. Oban Pro provides global concurrency and rate-limiting features for that purpose.
Concurrency limits control how much runs at once. They do not cap daily token spend. Track usage separately and set application-level budgets where needed.
Let approval wait in the database

A reviewer may take hours to approve a draft. There is usually no reason to keep a worker running for that entire time.
One straightforward design is to save the draft with a waiting_for_review status and finish the generation job. When the reviewer approves it, update the record and enqueue delivery in one transaction.
Record exactly which draft version was approved. Otherwise, a later edit could change what gets sent after the approval.
Also define what happens after rejection or no response. A separate scheduled job can check whether the review is overdue and notify the right person.
Oban Pro’s workflow and signal features provide another way to coordinate waiting steps. Either way, permissions, approval history, and escalation rules belong to your application.
Know what needs attention
A job that used all its attempts should lead somewhere useful: an alert, an operations view, or a clear status for the user.
Oban’s telemetry events provide job execution and failure signals. Connect them to your monitoring, then add the application details needed to understand the whole workflow.
Useful measures include queue wait time, retry count, discarded jobs, model usage, and time from request to completed result. Use a workflow ID to connect activity across jobs.
Keep credentials out of job arguments. Pass record IDs where possible and load only the data the worker needs. Review what goes into logs as well as what is stored in the job table.
Test recovery during deploys and forced shutdowns. Check the rescue behavior for your Oban version and engine; a stored job does not mean an interrupted task will resume at the exact line where it stopped.
When Oban Pro is worth considering
Open-source Oban is enough for many background tasks. A small sequence of jobs can also work well with application-owned state and transactional inserts.
Pro becomes worth evaluating when you need more involved dependencies: several jobs running in parallel, a later step waiting for all of them, or shared rate limits across nodes.
Compare the licensed features with the coordination code you would otherwise build and maintain. Start with the workflow you actually need.
Building this into your product
A good first implementation is one useful workflow with clear steps, saved results, and safe retries. Test it with timeouts, duplicate requests, and interrupted workers before increasing volume.
Elixirator helps teams build and improve Elixir applications, integrations, and AI features. If you need support with a workflow like this, explore our client work or get in touch.
Frequently asked questions
Is Oban an AI agent framework?
Oban is a background-job system. You can use it to run agent tasks, model calls, and tool calls, but your application or agent library defines the agent’s behavior.
Should every model call be an Oban job?
No. A short request that serves an immediate response may fit the normal request flow. Oban is useful when work can run in the background, needs retries, or must survive the user leaving the page.
Does Oban save the agent’s memory or model responses?
Standard job persistence does not save your full agent state automatically. Store the inputs, outputs, and progress you need in application records or another suitable store. Put their IDs in the job arguments.
Can I use Oban with an existing AI library?
Yes. A worker can call your existing AI code. Keep clear ownership of retries: retries in both the AI client and Oban can multiply requests if you do not coordinate them.
What happens if a worker crashes halfway through?
Recovery depends on the failure, configuration, and engine. A later attempt runs the worker again; it does not restore its in-memory state. Saved checkpoints and safe repeat behavior let it continue without unnecessarily repeating completed work.
Do I need Oban Pro for a multi-step workflow?
No. You can coordinate steps with your own records and job inserts. Pro offers workflow features that can reduce the code needed for more complex dependencies.
Does reliable execution mean reliable AI output?
No. A job can complete successfully and save an incorrect answer. Validate model output, evaluate quality, and add human review where the task needs it.

