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

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

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 next job picks up what the last job learned. It does not. A worker receives a job row and gets one payload from it: the args stored with that row. What the previous job concluded while it ran is not part of the next job unless application code saved it and the next job reads it back.

A job row holds an instruction, not a working memory

A River job is defined by a pair: an args struct that implements a Kind() method returning a stable string, and a worker with a Work method that receives the job and its typed args. When a job is inserted, River serializes the args to JSON and stores them in the args column as jsonb, alongside the kind, the queue name, the priority, the scheduled time, the maximum attempts, and the current state.

The state column tells the delivery story. A job can be available, scheduled, pending, running, retryable after an error, or finalized as completed, cancelled, or discarded. Maintenance services move jobs between those states: a scheduler promotes due jobs, a rescuer recovers jobs stuck in running after a crash, and a cleaner deletes finalized jobs once their retention period passes.

Read that schema as a whole and its purpose is clear. River can answer where a job is, when it should run, how many times it has been attempted, and how it ended. Attempts even keep their error details, so a failure can be inspected. No column answers what the job decided, what it learned about a record, or what a successor job should do differently because of it. The row is a delivery record. Reasoning never enters it.

Completed jobs are kept for a day, then deleted

One detail sharpens the point. River does leave completed, cancelled, and discarded jobs in the table after they finish, so operators can inspect recent work. By default that window is short: completed and cancelled jobs are retained for 24 hours, and discarded jobs for 7 days. After that, the cleaner prunes the rows.

Even inside that window, the kept row is an audit trail for people, not a briefing for the next run. It holds the same args the job started with. A value the worker computed, a branch it chose, or a summary it produced is not written back as input for a later job. A worker can attach metadata to its own job during execution, and that metadata stays on that row, for inspection and for that job's own retries. A different job inserted tomorrow does not inherit it.

Retries follow the same boundary. A retry is the same job row becoming available again with the same args and a higher attempt count. That is a repeat of one instruction. A new insert, even of the same kind for the same record, is a new row with its own args and no link to the earlier row's outcome.

Periodic jobs insert a fresh row every cycle

River's periodic jobs make the separation plainest, because they repeat by design. A periodic job pairs a schedule, an interval or a cron expression, with a constructor function. Each time the schedule comes due, the leader client's periodic enqueuer calls the constructor and inserts a brand new job from whatever args the constructor returns.

The enqueuer itself keeps its schedule state in memory. Its own documentation is direct about the consequence: when a process quits or a new leader is elected, scheduling starts over without regard for the state of the last scheduler. So even the timing layer carries nothing forward except the configured schedule. The new job for the next cycle is built from the constructor alone. It shares a kind and a queue with yesterday's job. It does not share yesterday's findings.

Unique jobs tell the same story. River can deduplicate inserts by args, period, queue, and state, so the same instruction is not queued twice in a window. That check compares instructions, says nothing about outcomes, and passes nothing to the job that follows.

Where an AI step inside a River job goes blind

Add a model call inside a worker and the gap stops being abstract. Consider a periodic job that reconciles new orders every hour, using a model to match payments, flag exceptions, and draft notes for the operations team. In the first hour, the model step produces real judgments: this customer already received a goodwill credit, that exception was already explained in a support conversation, this payment pattern is normal for one specific account.

An hour later, the periodic enqueuer inserts the next job. Its args carry the new batch window, not the last hour's judgments. Unless the earlier worker wrote those judgments into application tables that the next worker loads before calling the model, the next step re-derives settled questions and can contradict a decision already communicated elsewhere. River stored every job row correctly and pruned the old ones on schedule. Remembering judgments was never assigned to it.

Give the job a memory it can read before it reasons

The clean split is delivery versus memory. River handles delivery inside Postgres: transactional inserts, claiming, scheduling, retries, and periodic work. Memory needs a layer that every run, and every tool around the run, can read and write in one shared place.

Vilix AI is built for that second job. Vilix AI is cloud-hosted, so there is zero infrastructure to deploy or manage next to Postgres. The same memory is available everywhere over MCP, so a River worker, a chat assistant, a coding tool, and another scheduled agent can all work from one shared memory instead of separate notes. Vilix AI stores full conversation history, not just extracted facts, so a later run can revisit what was actually said and decided, in full context.

For the hourly reconciliation job, the pattern is direct. Before the model reasons, the worker retrieves the memory that matters: prior decisions for that account, earlier conversations, and rules settled in other tools. After it acts, it saves what it did and learned back to the same memory. The next periodic instance then starts from that record, not from its args alone.

Vilix AI is free forever on the free plan, with a 7-day Pro trial that requires no credit card. Data can be exported or deleted at any time.

River will keep remembering what it was designed to remember: a kind, its args, a delivery state, and a row for each run, kept just long enough to inspect. Give the AI steps inside those jobs a memory built for reasoning, and each recurring run stops starting blind.

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 Solid Queue Remember Between Runs? Jobs Are Stored in Your Database. The Next Job Still Starts With Only Its Arguments.

Does Solid Queue Remember Between Runs? Jobs Are Stored in Your Database. The Next Job Still Starts With Only Its Arguments. Solid Queue remembers jobs in a place that feels permanent: your own database. It is the database-backed Active Job backend that ships as the default in Rails 8, and every job is written as a row before a worker touches it. That row survives restarts, deploys, and crashed processes, so it is reasonable to assume the next run picks up where the last one left off. It does n