Moments & History
Overview
Agility does not overwrite data. Changes to assets are journaled, and each change is stamped with a moment — a chronologically increasing integer.
A few attributes are exceptions: rank order (Order) changes, for example, are not
journaled at all — an asset's history never shows a rank move. There is no way to
discover through the API which attributes are tracked.
A moment is the unit of time in the Agility data model. It is what makes the
Hist namespace, versioned asset addresses and asof queries possible, and it is
the single concept you must understand before you can read history correctly.
Moments Are Global, Not per-Asset
This is the detail that trips up most integrations.
Moment numbers are sequential for the entire Agility instance, not for an individual asset. Any change to any asset, by any member, consumes the next moment number in the system.
Consider three changes made in this order:
| Order | Change | Moment |
|---|---|---|
| 1 | A Story is created | 203 |
| 2 | That Story's ToDo is updated | 204 |
| 3 | An unrelated Member's Name is updated | 205 |
The Story now has two entries in its own history — moment 203 and moment 204 — and the next change anywhere in the system will be moment 206.
Because moments are handed out globally, an individual asset's moments are almost always sparse. A Story might have moments 203, 204, 818, 874 and 903. The gaps are other people's changes to other assets.
Never compute "the previous version" by subtracting 1 from a moment, and never treat the count of moments between two versions as a measure of elapsed time or of how much changed. To move through an asset's versions, read its history and use the moments it actually reports.
Addressing a Version
An asset is identified by its asset type and integer ID:
Member:20
A past version of an asset is identified by asset type, ID and moment:
Member:20:563
That reads as "the Member asset with ID 20, as it was at moment 563". The same
three parts appear in the URL of a versioned asset, in the Moment path segment:
/VersionOne/rest-1.v1/Data/Member/20/563
Omit the moment and you address the asset as it is now:
/VersionOne/rest-1.v1/Data/Member/20
Relations follow the same rule. A relation attribute on a past version refers to the past version of its target asset, so a historical query gives you an internally consistent snapshot rather than old attributes stitched onto current related assets.
Reading History: The Hist Namespace
rest-1.v1 exposes two query namespaces, and they differ only in whether you get
one record or all of them:
| Namespace | Returns | Root element |
|---|---|---|
Data | The current version of each matching asset | Assets |
Hist | Every historical version of each matching asset | History |
So this returns one Member:
GET /VersionOne/rest-1.v1/Data/Member/20
and this returns every version that Member has ever had:
GET /VersionOne/rest-1.v1/Hist/Member/20
Each Asset element in a History response carries an href that includes the
moment, giving you a direct, permanent address for that exact version.
History works at attribute granularity too. Asking for the history of a single
attribute returns one record per change to that attribute, each with an href
pointing at the attribute and an of pointing at the owning asset version:
GET /VersionOne/rest-1.v1/Hist/Member/20/Email
All of the ordinary query parameters — attribute selection, filtering, sorting,
paging and text search — work against Hist in the same way they do against
Data, subject to the same rule that list-oriented parameters only take effect on
list-shaped requests. See the query grammar
reference for the applicability matrix.
ChangeDate is maintained by historyChangeDate is populated by the history subsystem and records the wall-clock time
at which a version was written. Select it whenever you need to correlate moments
with real time, because a moment number on its own tells you ordering but not when.
Reading History as a Relation
The Hist namespace is not the only way to
reach history, and often not the most convenient. Every asset type also exposes
history through ordinary relations, which means you can traverse it with the
attribute-path syntax from any surface
— including query.v1 and the Bulk API,
neither of which has a Hist namespace of its own.
| Attribute | Type | From | Gives you |
|---|---|---|---|
History | Relation, multi-value | a current asset | every version of it |
Now | Relation | a version | the current asset — the reciprocal of History |
Prior | Relation | a version | the version immediately before it |
ChangedBy | Relation | a version | the Member who made the change |
ChangeDate | Date | a version | when the change happened |
ChangeComment | Text | a version | the comment supplied with the change |
Moment | Opaque | a version | the moment itself |
All seven are read-only as history attributes — you read them, you do not
select them to write. ChangeComment is the exception worth knowing: you cannot
set it by selecting it, but you can supply it at write time — ?comment= on
rest-1.v1 or a ChangeComment
key on api/asset
— and it then appears here.
Prior is what makes before-and-after comparison possible in a single query:
within one history row, Description is the value at that moment and
Prior.Description is the value immediately before it. Now.Description
reaches past both to the current value.
Now. is the query.v1 equivalent of the # operatorOn rest-1.v1 you write Parent#.Name to mean "the current value, not the
value at this moment" — and you must
percent-encode the # or lose it.
Now. does the same job as a plain relation traversal, so it needs no escaping
and works anywhere attribute paths work.
Finding What Changed
Query an asset's history as a nested result set, selecting both the value at each moment and the value before it:
{
"from": "Defect",
"select": [
{
"from": "History",
"select": ["Now.Name", "Prior.Description", "Description", "ChangedBy.Name"]
}
],
"filter": "Number=$Numbers",
"with": { "$Numbers": ["D-14519", "D-16384"] },
"page": { "start": 0, "size": 2 }
}
Each row where Prior.Description differs from Description is a change to that
attribute, attributed to ChangedBy.Name.
Restoring a Previous Value
There is no "revert" operation. Reading history and writing the old value back is
the supported approach: find the moment where the attribute changed, take
Prior.<Attribute>, and update the asset with it.
OID tokens in history results carry a moment suffix — Defect:1234:5678, not
Defect:1234. That third segment identifies a version, and passing it as the
from of an update targets a historical row rather than the live asset.
Take the first two segments only:
{ "from": "Defect:1234", "update": { "Description": "<the prior value>" } }
See addressing a version.
Point-in-Time Queries: asof
Moments are precise but opaque — you rarely know the moment number you want. The
asof query parameter lets you ask the question in terms of wall-clock time
instead:
GET /VersionOne/rest-1.v1/Hist/Story/1364?asof=2005-08-03T17:30:00.00&sel=Name,ToDo,ChangeDate
Two rules govern the result:
asofreturns the last historical record on or before the given time. It does not require a change to have happened at that instant. In the example above, the Story was last changed at 13:09:05.983 on 2005-08-03; that is the version returned for anasofof 17:30, because nothing changed in between.asofoverrides an explicit moment. If you supply both aMomentpath segment and anasofparameter, the moment is ignored. Use one or the other, not both.
asof applies to single-attribute, single-asset and multi-asset historical
queries alike, so you can reconstruct a whole project as it stood on a given day.
asof acceptsasof takes either an integer moment or a datetime in invariant culture, for
example 2005-08-03T17:30:00.00 — the same format ChangeDate returns. The full
syntax of asof and of the other query string parameters is covered in the query
grammar reference.
Practical Consequences
- Audit trails come for free. You do not need to build change tracking on top
of Agility; ask
Histfor the asset and selectChangeDatealongside the attributes you care about. - Trend and burndown data is a series of
asofqueries. To chart remaining effort over a sprint, issue oneasofquery per point on the x-axis rather than trying to reduce the raw history yourself. - Cache versioned URLs, not current ones. A URL containing a moment is immutable and safe to cache indefinitely. A URL without one is not.
- Moments are not timestamps. Two changes made in the same second get different moments; moments from different Agility instances are unrelated and must never be compared.
- History is voluminous. A
Histquery with no filter returns every version of every matching asset. Page it, and select only the attributes you need.
Related Topics
- Information model — the authoritative definition of asset type, attribute definition, asset, attribute and moment.
- Query examples — worked
Histandasofqueries with real response payloads. - REST API (rest-1.v1) — the endpoint surface that exposes all of this.
- Query grammar — the syntax of
asof,sel,where,sortandpage.