You need: vector composed and provisioned.
When you need it
The reprocess Worker is what runs, embedding models is the change that usually forces it, choosing your metadata is the other, and provisioning vector is what deploys the Worker.
You changed the embedding model. Every vector already in the index came from the old one, and a query embedded with the new model is asking a question in a language the corpus does not speak.
Or you added a metadata index, and the vectors written before it exists are invisible to any filter on that field — permanently, unless they are rewritten.
Run it
VECTOR_REPROCESS is a Workflow. By default it selects the documents whose stamped model differs from the configured one; it can also be told to re-embed everything.
The pagination is keyset, and that is the correctness argument
The default pass selects rows whose model differs — and then sets that model on the rows it writes.
So the result set shrinks underneath the scan.
Reading id > cursor in id order cannot skip and cannot repeat, whatever the predicate does to the rows behind the cursor.
Each page is one durable step
Workflow steps are journalled, so an instance that dies at page 4,000 resumes at page 4,000 with the same cursor rather than re-embedding four million documents.
Step names are derived from a page counter, so a replay asks for the same steps in the same order — which is what makes the journal usable at all. A step name that varied between runs would make every resume a fresh start.
The one thing it does not pick up
A row written during the run whose id sorts behind the cursor is not seen.
That is the honest cost of keyset order, and it is not a problem in practice: a document written after the model changed is embedded with the new model by the write path anyway.
The pass exists for the backlog, and the backlog does not grow behind it.
An interrupted run leaves a coherent state
Each written row is stamped with the new model. Because the stamp is what the default selection reads:
The documents already done have the new model and will not be selected again. The rest are still waiting.
Re-running picks up exactly what is left.
Batching
Upserts go out in batches bounded by the binding’s 1,000-vector ceiling. The HTTP API allows 5,000; the binding does not, and the number that matters is the one your code will hit.
Adding a metadata index
Provision first, then reprocess. In that order.
Provisioning creates the index; the reprocess pass rewrites the vectors so their metadata lands in it. Reversing the two rewrites vectors into an index that does not exist yet, which is the original problem with extra steps.