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 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 deleted, its return value is stored nowhere, and the next job with the same task name arrives knowing only the payload it was handed.
What Graphile Worker actually persists
A job enters through addJob() in JavaScript or graphile_worker.add_job() in SQL. What lands in the jobs table is a task identifier, a JSON payload, a run_at time, a priority, an attempts counter, and a maximum attempts value. A worker claims a due row, using SKIP LOCKED so parallel workers do not collide, and Postgres LISTEN/NOTIFY keeps pickup latency low.
Read that row closely and its purpose is clear. Every field answers a delivery question. Which function should run. With what arguments. Starting when. How many times it has already been tried. Nothing in the row answers a work question: what the previous run found, which records it already handled, or what it decided. The table is a queue, shaped exactly like a queue, and a queue is not a memory.
Success deletes the job
The sharpest detail in Graphile Worker is the success path. A task executor is an async function that receives the payload and a set of helpers. If the executor returns, the job is deemed a success and is deleted from the queue. If it throws, the job is deemed a failure and is rescheduled with exponential backoff.
Notice what returning means here. The return value goes nowhere. There is no result store, no completed-jobs archive, and no history table to query later. Returning is simply the signal that the row can be removed. Graphile Worker deletes completed jobs on purpose, because the fastest jobs table is a small one, and the design treats a finished job as clutter rather than as a record.
The job_key feature underlines the same point. A key can deduplicate or replace a pending job, but completed jobs are deleted and there is no permanent job_key log, so asking about a completed key afterwards is a no-op. The system remembers a job exactly as long as the job is unfinished, and not one moment longer.
Failure is the only run that leaves a trace
The one exception proves the rule. A failed job stays in the table. Its attempts counter increments, its last_error field keeps the most recent error, and its run_at moves forward under exponential backoff, by default up to 25 attempts over roughly three days. A permanently failed job remains in place so the failure can be debugged.
So the only history Graphile Worker keeps about a run is the history of a run going wrong, and even that history is a single error string and a counter on a row that exists to be retried or cleaned up. A successful run, the run whose output the next run actually needs, leaves nothing behind at all. An AI step that summarizes new records, drafts a reply, or updates a shortlist produces its most valuable work on the success path, and the success path is the delete path.
A recurring job is a new job, every time
Recurring work does not change this. Graphile Worker's crontab support creates a fresh job at each scheduled time, through the same queue, with the same task identifier. The payload can carry a _cron object with the timestamp the job was due and a flag for whether it was backfilled after downtime.
That timestamp is scheduling metadata, not accumulated knowledge. The Tuesday instance of a job and the Wednesday instance share a task name and a payload shape, and nothing else. Neither instance can see what the other one read, wrote, skipped, or concluded, because the earlier instance was deleted when it succeeded. A daily AI job therefore starts each morning with its arguments and a blank working memory, no matter how many mornings it has already run.
What an AI job actually needs to remember
Put an AI step inside a Graphile Worker job and the gap becomes concrete. The job checks a source, reasons over what it finds, and acts. On the next run it needs to know which items were already processed, what the last summary said, which decisions were already made, and what a human corrected last time. None of that fits in a payload, because the payload is written before the run starts, by whoever enqueued the job. The run's own learning has no home to go to.
The usual workaround is a hand-built table beside the queue, plus code that reads it at the start of every run and writes it at the end. That table then needs shaping, pruning, and retrieval logic, and it serves only the one job type that writes to it. A second automation, running in a different tool, cannot see it at all.
One memory the next run can actually read
Vilix AI is built for exactly that missing layer. Instead of each job keeping its own private notes, every run reads from and writes to one shared memory, so the context a run produces is still there, in full, when the next run starts.
Vilix AI is cloud-hosted with zero infrastructure to run: no database to provision, no queue-side table to design, and nothing to maintain. The same memory is available everywhere over MCP, so a scheduled job, a chat assistant, and a coding tool all work from the same context instead of separate copies. It stores full conversation history, not just extracted facts, so the reasoning and corrections behind a decision survive alongside the decision itself. And the terms keep the trial honest: a free plan forever, a 7-day Pro trial with no credit card, and the ability to export or delete everything at any time.
The pattern is simple to add to a Graphile Worker setup. The job still receives its payload and does its work. At the start, it pulls the relevant memory for that workflow. At the end, it saves what it learned. Graphile Worker keeps doing what it does well, which is delivering the job reliably. The remembering happens in a layer designed for it.
Keep the queue, fix the forgetting
Graphile Worker's answer to memory is precise and narrow: a job is remembered until it succeeds, and then it is deleted by design. That is the right behavior for a job queue and the wrong foundation for an agent that runs every day. Give the recurring job one memory it can read on every run, and each run stops starting over.
Get Started for Free — Free forever, no credit card.