Free forever, no credit card.Get Started for Free →
← All posts
November 9, 2026 · 6 min read

Does Oban Remember Between Runs? Jobs Are Stored in Postgres. The Next Job Still Starts With Only Its Args.

Does Oban Remember Between Runs? Jobs Are Stored in Postgres. The Next Job Still Starts With Only Its Args. Oban remembers jobs better than most background job libraries. Every job is a row in PostgreSQL, in the oban_jobs table inside the application's own database, so jobs survive restarts, deploys, and crashes, and they are backed up together with the data they relate to. That durability is real. It is also easy to misread. When a job runs, its worker receives one job struct: the worker name,

Does Oban Remember Between Runs? Jobs Are Stored in Postgres. The Next Job Still Starts With Only Its Args.

Oban remembers jobs better than most background job libraries. Every job is a row in PostgreSQL, in the oban_jobs table inside the application's own database, so jobs survive restarts, deploys, and crashes, and they are backed up together with the data they relate to. That durability is real. It is also easy to misread. When a job runs, its worker receives one job struct: the worker name, the queue, and the args map stored with that row. What the previous run figured out is not in that struct unless the previous run deliberately wrote it somewhere the next run knows to read.

A job is a stored row, not a continuing conversation

The model is easy to inspect. A worker is an Elixir module that uses Oban.Worker and defines a perform/1 function. Code builds a job with the worker's new/2 function, which converts an args map into a changeset, and enqueues it with Oban.insert/1, which writes the row to Postgres. Later, a queue process fetches a due row, builds an %Oban.Job{} struct from it, and calls perform/1 with that struct.

Open the row and the memory question answers itself. The columns describe an instruction and its bookkeeping: the worker module, the queue, the state, the args stored as jsonb, an optional meta map, the attempt count and maximum attempts, timestamps for insertion, scheduling, and completion, and an errors list that records each failed attempt. The args field is the payload supplied at enqueue time, such as an account ID, a report type, or a list to process. It is an input. It is not a running record of what earlier executions learned.

The details confirm the split. Args keys always come back as strings, because the map is serialized into jsonb. A retry runs the same row again, so the worker can pattern match on the attempt count and behave differently on attempt four than on attempt one. Completed rows are retained briefly for metrics, then pruned, by default after about sixty seconds in recent versions. None of that turns the row into a memory. The row records that work was requested and what happened to the request. It does not accumulate findings.

What Postgres actually keeps between runs

Three kinds of things persist, and it helps to name them precisely.

First, the schedule persists. A job inserted with a future scheduled_at waits in the table until it is due. Recurring work persists as a pattern: the Cron plugin reads a crontab-style configuration and inserts a fresh job when each entry comes due. Each insertion is a new row with the args written in the configuration or computed at insertion time. Tuesday's cron job and Wednesday's cron job are siblings, not the same job continuing. Nothing flows from one row into the next on its own.

Second, the delivery bookkeeping persists. States move from available to executing to completed, retryable, cancelled, or discarded. Errors from failed attempts are appended to the row. Uniqueness checks compare worker, queue, and args so a duplicate job is not inserted inside a configured window. That is coordination and audit data, and it is genuinely useful for operating a queue.

Third, whatever application code writes for itself persists. A worker can update its own job row, write results into an application table, or store a checkpoint keyed by an ID in args. Those writes are the whole bridge between runs, and every part of that bridge is manual: deciding what to save, choosing where it lives, handling concurrent updates, and teaching the next run to look for it.

Where an AI step inside an Oban job goes blind

Add an AI step to a worker and the gap stops being abstract. Consider a worker that runs every morning, asks a model to review new signups, and flags the ones that need a human look. On Monday the model works through forty accounts and, in the process, settles several judgment calls: one reseller pattern is legitimate, one naming pattern signals a test account, one customer was already cleared last week under a slightly different name.

On Tuesday the cron plugin inserts a new row. The worker starts, perform/1 receives the new job struct, and the struct contains the queue, the schedule, and the args that were configured when the job was created. The judgment calls from Monday are not in args, because args were fixed before Monday happened. They are not in the completed row either, beyond the fact that it completed. The model starts Tuesday with the same briefing it had on Monday morning and re-derives, contradicts, or repeats Monday's work, depending on the day.

The standard fixes all rebuild memory by hand. Store the model's conclusions in a table and load them in every worker. Keep a rolling summary in the meta field or in a settings row and pass it forward at insertion time. Write decisions into application records and hope the next prompt remembers to query them. Each fix works for the one workflow it was built for. None of it is shared with the other workers, the other tools, or the next agent added to the stack, and each new consumer starts the plumbing project again.

The missing layer is shared memory, not better job storage

Oban's storage was never the problem. Postgres is an excellent place for jobs, and the row model is exactly right for reliable delivery. The problem is that a job row answers the question "what should run, with what inputs" and memory has to answer a different question: "what is already known, across every run and every tool, so this run does not start from zero."

That layer is what Vilix AI provides. Vilix AI is cloud-hosted, so there is no database to provision, no container to run, and no infrastructure to manage on top of the Postgres an Oban application already operates. A worker connects over MCP, and the same memory is available to every other connected tool and agent, not just to one Elixir process: the reasoning captured during Monday's run can be read by Tuesday's job, by a different worker, and by an assistant in a completely different tool.

The depth matters as much as the sharing. Vilix AI stores full conversation history, not just extracted facts, so a later run can revisit what was actually said and decided, including the reasoning behind a call, instead of working from a compressed note. A scheduled agent that can read that history stops re-litigating settled questions, because the settlement itself is in memory, in full.

The starting terms keep the experiment small. Vilix AI has a free plan forever, and a 7-day Pro trial with no credit card required. Everything stored can be exported or deleted at any time, so memory stays portable and under the account owner's control.

Oban will keep remembering jobs, reliably, in Postgres, exactly as designed. Give the workers a shared memory outside the job table, and the next run finally starts from what the last run learned.

Get Started for Free

Persistent memory across ChatGPT, Claude, and the AI tools you already use in Vilix AI.

Get Started for Free

Free forever, no credit card.

Keep reading
Does Graphile Worker Remember Between Runs? A Successful Job Is Deleted, and Its Return Value Is Stored Nowhere.

Does Graphile Worker Remember Between Runs? A Successful Job Is Deleted, and Its Return Value Is Stored Nowhere. Graphile Worker stores every job as a row in PostgreSQL, so the queue itself is durable, inspectable, and transactional. That durability creates a natural assumption: if the jobs live in a real database, the next run of a job should be able to look back at the last run. It cannot. Graphile Worker keeps a job only while the job still needs to run. The moment a job succeeds, its row is

Does Huey Remember Between Runs? The Result Store Deletes on Read, and Periodic Tasks Discard Their Return.

Does Huey Remember Between Runs? The Result Store Deletes on Read, and Periodic Tasks Discard Their Return. Huey keeps three things in its storage backend: a queue of pending task calls, a schedule of delayed and periodic work, and a result store for return values. Because all three can live in Redis, Postgres, or SQLite, it is easy to assume the next run of a task can see what the last run produced. It cannot, at least not in the way an AI task needs. Huey stores delivery state and one return

Does River Remember Between Runs? Jobs Are Stored in Postgres. The Next Job Still Starts With Only Its Args.

Does River Remember Between Runs? Jobs Are Stored in Postgres. The Next Job Still Starts With Only Its Args. River remembers jobs in Postgres, in the same database as the application data. That design is the reason teams choose it: a job can be inserted inside the same transaction as the changes it relates to, so the job exists if the transaction commits and disappears if it rolls back. Every job sits as a row in the river_job table. That durability makes a second assumption feel safe, that the