How Writes Work
Writing content in FoxNose is not a single write to a database. Every change runs through a revision pipeline that keeps your records, their embeddings, and their search indexes in sync — so what your agents retrieve always matches what you published.
The Revision Pipeline
When you write content, you are working with a Revision — a versioned change to a single Resource. Each Revision moves through the same ordered stages:
- Create or update a Revision. You author or edit the content as a draft Revision.
- Validate. The content is checked against the Collection's current schema.
- Vectorize. Embeddings are generated for the vectorizable fields.
- Index. The records and their vectors are written into the search indexes.
- Publish. The Revision becomes live.
Until you publish, nothing is served. This gives you a safe draft-then-publish workflow: you can prepare and validate changes without affecting what applications and agents see.
Flux Serves Only Published Content
The Flux API — your read and delivery surface — returns only published Revisions. Drafts, in-progress edits, and unpublished changes are never exposed through Flux. Publishing is the single, explicit moment when a change goes live.
When Two Writers Meet
The pipeline above describes one writer. Two are a different problem, and it is the ordinary case once agents are involved: both read the same record, both compute an update from what they read, both publish. The second publication supersedes the first, and neither writer can tell that anything was lost.
FoxNose does not resolve that for you — it gives you the means to detect it. Every read carries the revision it was served from (_sys.revision at the edge, key on a Revision object). Send that value back as If-Match on Create Revision, Upsert Resource or the Flux PUT update route — the writes that accept a precondition — and it applies only if the record is still at that revision:
curl -X POST ".../resources/:resource/revisions/" \
-H "If-Match: 8f2b1c9d" \
-d '{"data": {"title": "Updated"}}'
If it moved on, the write is refused with 412 rather than applied, and the response names the revision that is current now — so the correct recovery is to re-read, recompute against the new state, and write again. That is a different situation from a 409, where two writers merely collided on the next revision number and nothing was lost: there, retrying the same write is right. Conditional writes sets out all three outcomes.
The check is evaluated under a row lock inside the transaction that inserts the revision, so it cannot pass and then go stale before the write lands.
A precondition is optional. Omit If-Match and writes behave exactly as before — last write wins. Add it where losing an update would matter.
Keeping History Bounded
Every write creates a revision, so a long-running process accumulates history. A collection can trim its own, in one of two ways — and which one fits depends on what the intermediate steps are for:
auto_remove_revisionsholds a resource to N live revisions, trimming on every write: oldest first, whatever their status, and never the current revision. This suits human editing, where only the latest version matters and older ones are a safety net.remove_drafts_on_publishkeeps every step while work is in progress and removes a resource's lower-numbereddraftrevisions once a revision is published. This suits agent runs, where the whole path matters until there is a result — and stops mattering the moment there is one.
They are two answers to "when do intermediate revisions stop being needed", not two limits, so a collection enables at most one. Note the difference in what each may remove: the publication-based mode only ever removes draft revisions, while the count-based one trims to its limit regardless of status — published revisions included. Both always keep the resource's current revision.
Records, Embeddings, and Indexes Stay in Sync
Because vectorization and indexing are stages of the same pipeline, embeddings update on publish automatically. You never generate embeddings yourself, run a separate indexing job, or reconcile a vector store against your records. When a Revision publishes, its records, its embeddings, and its search indexes all advance together.
This is what lets FoxNose combine structured, keyword, and semantic retrieval in a single call without drift: there is no window where the vectors describe an older version of the content than the records do.
Write Operations and Billing
Write operations are volume-weighted. A single request counts as 1 write for content up to roughly 500 embedded tokens; beyond that, it scales in proportion to the amount of content embedded:
write_operations = max(1, ceil(embedded_tokens / 500))
The floor is always 1, so a small write is always exactly one write. A larger write — say around 1,500 embedded tokens — counts as 3. This weighting reflects the real embedding cost of a write and keeps a single large import from being billed the same as a tiny edit.
For current write rates and plan allowances, see Billing & Usage.
Related
- Schema Migrations → How schema changes interact with existing Revisions.
- Management API — Revisions → Create, update, and publish Revisions programmatically.
- Billing & Usage → How writes are metered and priced.
- Conditional writes → The
If-Matchprecondition and the three outcomes it produces. - Revision retention → The two ways a collection trims its own history.