Skip to main content

Knowledge Base Document Versioning and Rollback

Re-uploading a document used to be a one-way door. Post a file under an existing name and the previous content was deleted along with its chunks and retrieval vectors — so a bad re-index or a broken connector sync could silently degrade the answers your AI agent grounds in, with no way back. Document versioning closes that gap: every time a document is replaced, the superseded version is captured as an immutable snapshot you can inspect, diff, and roll back to. Agents have carried version history for a while; their knowledge now does too. This guide walks the full flow: when versions are captured, how to read the lineage, how to diff before you promote a change, how to roll back, and the retention ceilings and guard rails that bound the feature. The endpoint paths below are relative. Send them against https://api.orbit.devotel.io/api/v1. This guide assumes a knowledge base with documents already loaded — see Build and Maintain an AI Knowledge Base for the create/load/attach flow.

When a new version is captured

A version snapshot is written every time a document’s content is replaced under the same identity. Three paths replace content:
  • Content re-upload. Posting a document with the same knowledge base and name again (via POST /knowledge-bases/:id/documents) replaces the existing document. The replaced content is snapshotted first, then the new upload becomes the new head version.
  • Connector and refresh syncs. A managed connector resync, a scheduled refresh of a URL/RSS/Notion source, or a forced POST /knowledge-bases/:id/refresh that pulls changed content goes through the same replace path — so connector drift never destroys the last known-good copy.
  • Rollback. Restoring an earlier version is itself a content replace, so the version you rolled back from is snapshotted too. Nothing is ever destroyed.
Manual redos and partial edits count as re-uploads: any second upload under the same name snapshots the prior content, whether it was better or worse. Versioning is automatic — there is no flag to set, and no version is created for the very first upload (there is nothing to supersede).

Read a document’s version lineage

List the current head plus every retained snapshot of a document:
The response carries two parts:
  • current — the live head version, marked is_current: true.
  • history — the retained snapshots of superseded versions, newest first.
Each entry (current and historical) describes one version:
  • version — the 1-based version number. Use this in the diff and rollback calls.
  • name / type — the document’s display name and content type at that version.
  • size_bytes, chunk_count, char_count — how big the version was and how many retrieval chunks it produced.
  • content_hash — a normalized SHA-256 of the version’s text. Two versions with the same hash are byte-equivalent after whitespace and unicode normalization, so the hash is the cheap “did this sync actually change anything?” check before you open a diff.
  • created_at / superseded_at — when the version was first uploaded and when it was replaced (the uploaded-by timestamp pairing you audit against a deploy or sync window).
  • rollback_available — whether that snapshot still carries its full content and can be restored (see the retention ceilings below).
Documents uploaded before versioning existed report as version 1 with an empty history; their first replacement starts the lineage at version 2.

Diff two versions

Compare any two versions before you trust a change in front of customers:
from and to are both required 1-based version numbers. The response is a line-oriented diff:
  • added / removed / unchanged — line counts, so you see the scale of the change at a glance.
  • hunks — a unified-style excerpt where each line is prefixed with (unchanged), - (removed), or + (added). Read the hunks like a code review: verify the policy wording that changed is the wording you meant to change before the new version grounds live answers.
  • truncatedtrue when the excerpt was capped. Hunk output is capped at 400 lines, and for very large document pairs the endpoint degrades to the counts only (no hunks) rather than stall on a pathological diff.
Two rules to keep in mind:
  • The head version diffs against live content; older versions diff against their retained snapshots. A snapshot whose content was dropped (over the size ceiling — see below) returns 422, so prefer diffing against versions whose rollback_available is true.
  • An identical content_hash on two versions predicts an empty diff — skip the call.

Roll back to an earlier version

Restore a snapshot exactly as returned by the versions list:
The endpoint returns 202 with the new document record. 202 means accepted, not finished: the restored content is re-chunked and re-indexed asynchronously — the document’s previous chunks and retrieval vectors are replaced as the rebuild runs. Poll GET /knowledge-bases/:id/documents until the document’s indexing status settles back to indexed before benchmarking or declaring the rollback done. Rollback is additive, never destructive:
  1. The current head is snapshotted into history first — exactly the same as if a human had re-uploaded over it.
  2. The target version’s content is then re-uploaded and becomes the new head version.
Because rollback goes through the normal replace path, it is fully reversible. Rolled back too far, or to the wrong version? The version you left is now sitting in history — list versions and roll back again. Expect a 409 in two cases: the target version is already the current head, or the target snapshot no longer carries its content (rollback_available: false). An unknown version number is a 404.

Retention ceilings

History is bounded so the lineage stays cheap to store and fast to read:
  • Count ceiling. At most 10 snapshots are retained per document — the most recent ones. A document on its 40th upload answers about versions 30–40; older snapshots fall off.
  • Size ceiling. Snapshots retain their full content for rollback up to 1 MB of text per version. Above that, the snapshot stays in history as metadata only (name, counts, hash, timestamps) with rollback_available: false — you can still diff metadata and audit what changed, but that specific version cannot be restored and cannot be content-diffed.
Retention is per document, not per knowledge base, so one thrash-y document never eats another document’s history.

Guard rails and permissions

  • Scopes. The two reads (GET .../versions, GET .../versions/diff) need the knowledge:read scope — listing version metadata is not a privileged action. The rollback (POST .../rollback) is a write: it requires knowledge:write plus an owner, admin, or developer role, the same guard as uploading a document.
  • Audit. Every rollback is written to the workspace audit log with the document, the restored version, and the actor — rolling back the ground-truth an agent answers from is always traceable. Reads are not logged.
  • Tenancy. Lineage lives on the document inside your organization’s knowledge base; no other workspace can see it, and the same organization-scoped isolation that guards the base guards its history.
  • Reversible by design. Because rollback itself snapshots the head, there is no destructive path in this feature — the worst outcome of a mistaken rollback is one more entry in the history list.

Troubleshooting

See also