Who this is for: whoever is pointing Revetly at a project that already has years behind it. You need: an instance whose ordinary pass already works, and a decision-maker for the bill — reading history is the one step here that can cost real money, and on an instance declaring no provider it costs none. Not here: how a connector serves history (writing-a-connector.md §7c) and what each source declares (connector-contract.md).
An ordinary pass reads the current state of every item in scope. What it never reads is the record of how any item got that way — the state transitions, the field changes, the comment thread where the decision was actually argued. So a five-year backlog arrives as three thousand items that all appear to have been decided the morning of the first ingest, and that is the impression that then persists.
Your sources may hold that record. This page is how you find out, what it costs, and what a corpus that was read back only so far will and will not answer.
Nothing on this page starts by itself. Not init, not the first ingest,
not watch, not any MCP tool. A backfill is the largest spend and the largest
provider exposure an instance will ever authorise, so it is named, priced, and
left to you — the same shape revetly migrate and the update endpoint have, and
for the same reason.
revetly doctor
[PASS] connector history todoist: serves state-transitions, comments; windowed over
source time; watched for change: comment; horizon: completed
work back to the project's creation date …
→ todoist can serve a backfill — `revetly backfill
--dry-run` prices what that would cost, per window, without
sending anything. Nothing starts one unasked
Read off each connector's own declaration. No source is called and nothing is spent — an instance deciding whether adopting a decade is worth it must not have to run the pass to find out what is on offer.
A connector that declares no history is not defective; it is the honest
answer for most sources, and revetly backfill then refuses it by name rather
than returning an empty result you would read as "this tracker had no history".
revetly backfill with no other argument says the same per source, and
revetly connectors list carries it beside the rest of the inventory.
The declaration has four parts worth reading rather than skimming:
| part | why it is not one bit |
|---|---|
| depths | state-transitions, field-revisions, comments — sources differ, and a tracker with no per-field change log can serve two of the three and never the third |
| windowed | whether the source can be asked for a bounded slice at all. windowed=False means narrowing costs the same as the whole archive, and you are entitled to know that before you pay for it |
| horizon | how far back the source can see. Sometimes the account's plan decides it, in which case the plan is read during the pass and said in the window's notes |
| beyond it | what falls outside that horizon, so the silence is legible |
Every repo-backed source — pm, docs, conversations, deliveries — serves
field-revisions out of the repository its roots sit in. There is no API, no
token, no rate limit and no vendor: five years of specs in docs/ is five years
of how those specs changed, in a local checkout. For a prose tree it is the only
history there is.
What that buys, and what it does not:
review_log
line, an ingest-side stamp — is not a version of the ask, produces no artifact
and costs no provider callFree to read is not free to extract: prose is dense, and §2 and §3 below are what bound it. A root that is not inside a git repository serves no history, and the declaration names that root rather than letting you read the silence as "there was nothing".
Azure DevOps answers every depth the declaration knows about, and it is the source most enterprise backlogs actually live in. What that means in practice:
revisions and state transitions come off the reporting revisions feed,
which walks every revision of every work item in ascending change order. A
window opens it at its own startDateTime; nothing in the API closes a range,
so the end is enforced by the connector. Its continuation token is the resume
point inside a window, kept in the connector's state slot and written only
once that window has landed — backfill.json still checkpoints at window
boundaries, which is what a resumed run skips whole windows on
most revisions are worth nothing and cost nothing. ADO writes one on every
save, so a decade is mostly a priority nudged and a sprint re-planned. A
revision produces an artifact only when it moved a field this instance
declared interesting — history_fields, defaulting to state, assignment,
description and acceptance criteria. That is the difference between a few
thousand episodes and tens of thousands, and it is the one lever worth setting
before you price a backfill:
[connectors.azure-devops]
history_fields = ["System.State", "System.AssignedTo", "System.Description",
"Microsoft.VSTS.Common.AcceptanceCriteria"]
comment_threads_per_pass = 0 # 0 = every thread that has a comment in it
comment_versions = false # what comments USED to say: a request each
comments are read on the ordinary pass, not on the backfill. ADO keeps
closed work items queryable, so every thread is already inside a snapshot's
view — and a snapshot is the only pass that can ever notice a comment being
deleted, because a historical one tombstones nothing. System.CommentCount
rides the work item, so an item with no thread costs no request at all
a comment can still be edited, and ADO announces the edit with nothing but
a bumped version. So a comment is gated on its bytes rather than on its
identity: an edit costs one episode and CONTEXT holds what the source now
says. Revisions keep identity gating, because an ADO revision never moves —
which is what keeps a decade of them extracted once and never re-billed
comment_threads_per_pass bounds what one pass spends on threads on a
large board. A pass that hits the bound says its view of comments was
partial and withdraws none of them; the work items keep their own deletion
rule, and successive passes come round the whole board — on the ordinary tick
interval, because a pass that stopped early records that it owes itself a read
and the next check answers on it without asking the source
what a comment used to say is comment_versions = true, off by default
because it is one request per comment. On, a backfill lands each superseded
version as its own immutable record
GitHub Issues is read the same way, in the shape the read connector states:
updated_at. An event becomes an immutable transition artifact only when
its own created_at falls inside the windowcomment_threads_per_pass,
and are bounded and rotated the same way ADO's arerevetly backfill --dry-run
Every source asked for, tiled into windows over source time, each window read from the source and priced against this instance's own measured history — and nothing sent.
todoist: serves state-transitions, comments; windowed over source time; …
2019-01-01 → 2019-04-01: read 84, would send 84 (12 episode(s), ~$0.34)
…
across 1 of 4 source(s) — todoist — the history on offer is 148 episode(s) in 12 window(s)
roughly $4.02 in total, as the sum of 1 separate budgets — an estimate, not a bill
nothing was sent, and nothing here starts unattended. Reading it is one source at a
time, under a ceiling you declare:
revetly backfill --connector todoist --run --max-episodes 148
Notes on that output:
--since is needed. Each source is asked for the whole of what it
declares it can reach, because "what could my past cost" is the question, and
having to guess a date first is being asked to answer it yourself.
--since / --until narrow it when you want one quarter priced.--max-episodes /
--max-spend on the invocation. A declared [backfill] ceiling would stop
the sweep partway and the number you asked for would stop being the price of
the history on offer. Pass one explicitly to ask "where would my ceiling stop
this".priced from: says whether the figure is a measurement of this corpus or
a projection from another one. An instance that has ingested nothing has
measured nothing, and the line says so.--show-bodies renders exactly what would leave for the provider, with
the sensitivity scan run over it. A decade of tickets is the highest-volume,
highest-sensitivity payload this instance will ever send.revetly backfill --connector todoist --run --max-episodes 148
One source at a time, under a ceiling you declare, checkpointing at every window
boundary. docs/failure-and-staleness.md §"When a decade is still loading" has
the whole of what stopping, resuming and failing mean; the short version:
.revetly/backfill.json survives a restart, and resuming
re-sends nothing — a covered window is not even read. revetly reset removes
it with the corpus it describes--allow-sensitive — the opposite of an ordinary pass, because
a backfill is attended by definitionAn instance that ran a bounded backfill holds part of a history, and every read of it is a read of a partial past. Two questions look identical from a result set and only one of them misleads:
CONTEXT has nothing before 2023. Nobody has paid to read before 2023.
So the floor somebody actually paid to reach is reported wherever staleness is reported, and it outlives the command that set it:
context_health carries history: per source, how far back it was read,
the windows covered, and whether any of the range asked for is still unread.
The same sentence rides each sources row beside how recently that source was
read — freshness answers the top of the range, this answers the bottom.search_context, get_artifact, get_authoring_context,
list_backlog — carry a history key once the project has a floor at all, set
by a backfill or by a repository floored at its connection (§5), naming those
floors. A query about anything older is answered out of a past nobody has read.
The floors are the named project's own, and a read spanning several projects
carries each one's under its name.revetly status prints the span and the floor beneath it.This is deliberately not the context_health staleness key. An unfinished or
bounded backfill is not a failure and does not make the corpus stale: the last
pass succeeded, and what CONTEXT holds is exactly what was paid for. Reporting
one as the other would earn a stale warning nobody can act on.
Almost every source has a current state an ordinary pass reads whole. A git
repository does not — its history is its snapshot — so the commits connector
reads each repository forward from the moment that repository was connected to
this instance, and no further. That moment is recorded in
.revetly/connections.json, per repository, and is never re-derived from the
current pass: a repository connected last month still reads from last month.
What that means in practice:
repos writes it ([connectors.commits.since]). A bare since = "…" is the
older, connector-wide shape: still honoured, and revetly doctor says it is
one floor standing in for every repository under it.revetly backfill, priced per window, as everything
else on this page is.revetly doctor reports the floor per repository and where it came from —
declared, or the connection — and revetly status and the read tools report
how far back each repository has actually been read.revetly backfill says it has no unread past, by name, and sends nothing.init does not.