Relations in Agility
Relations are attributes whose values are other assets. They form the graph that makes Agility's model expressive — and the query surfaces let you traverse that graph in a single request instead of stitching results together client-side.
Two Shapes
- Single-value — the asset links to at most one other asset:
Scope,Status,Priority,Team,Timebox,Parent,Super - Multi-value — the asset links to any number of others:
Owners,Children,Dependencies,Dependants,Requests,AffectedByDefects
What they look like in a query.v1 response — selecting a relation bare
returns an OID object (or an array of them); selecting through it with a
dot returns a separate, flattened key, not a merged object:
// select: ["Scope", "Scope.Name", "Owners"]
[
[
{
"_oid": "Story:1207",
"Scope": { "_oid": "Scope:1308" },
"Scope.Name": "Release 1.0",
"Owners": [{ "_oid": "Member:1035" }]
}
]
]
To project several attributes of related assets as grouped objects, use a nested subquery — see Attributes.
Frequently Used Work-Item Relations
| Relation | Value | On | Meaning |
|---|---|---|---|
Scope | single | work items | The containing project |
Status | single | work items | Workflow list value |
Priority | single | primary workitems | Priority list value |
Team | single | primary workitems | Owning team |
Timebox | single | primary workitems | Sprint / iteration |
Parent | single | see below | Task/Test → owning primary workitem; Story/Defect/TestSet → owning Theme; Theme → parent Theme |
Super | single | primary workitems, Epics | The owning Epic; Epics nest via Super/Subs |
Owners | multi | work items | Assigned members |
Children | multi | primary workitems | Owned Tasks and Tests |
Dependencies | multi | primary workitems | Upstream items this one depends on |
Dependants | multi | primary workitems | Downstream items that depend on this one |
AffectedByDefects | multi | primary workitems | Defects impacting this item |
There is no BlockedBy attribute — dependency questions are asked through
Dependencies and Dependants. As always, when a name surprises you,
check meta.v1.
Traversing the Graph
Dots traverse to any depth, and each path arrives as its own flattened key:
{
"from": "Task",
"where": { "ID": "Task:1469" },
"select": ["Name", "Parent.Number", "Parent.Scope.Name"]
}
[[{ "_oid": "Task:1469", "Name": "Design Login Page",
"Parent.Number": "S-01001", "Parent.Scope.Name": "Release 1.0" }]]
Filtering traverses the same way — and works on the relation itself, where you should compare OIDs, not names:
{
"from": "Story",
"where": { "Scope": "Scope:1308" },
"filter": ["Owners='Member:20'", "Status.Name='Done'"],
"select": ["Number", "Name"],
"page": { "size": 50, "start": 0 }
}
Paths can also carry inline filters in brackets
(Owners[AssetState!='Dead'].Name) — see
attribute paths.
Every Relation Has a Reverse
Relation definitions come in reciprocal pairs, discoverable in meta.v1
(ReciprocalRelation): Story.Owners ↔ Member.OwnedWorkitems,
Task.Parent ↔ Story.Children. So "this story's owners" and "this
member's stories" are both one hop — pick whichever direction starts from
the asset you have.
Derived and Rollup Relations
Many convenience relations are read-only projections maintained by the system — you write the underlying relation and read the convenience:
-
Task.Scopeis read-only — a task lives in its parent's project. Move the story and its tasks follow; you cannot scope a task separately. -
Scope.ParentMeAndUp/Scope.ChildrenMeAndDown— a scope plus all its ancestors / descendants. These power the single most useful filter idiom in Agility, "in this project or any project beneath it":{"from": "Story","filter": ["Scope.ParentMeAndUp='Scope:1308'"],"select": ["Number", "Scope.Name"],"page": { "size": 50, "start": 0 }}It reads backwards until it clicks: the target scope is among the ancestors-or-self of the story's scope — i.e. the story is in the target's subtree.
-
Epic.Subs,Scope.Workitems,Team.Workitems— read-only rollups of the corresponding forward relations.
Modifying Relations
Set single relations and add/remove on multi relations, on either write
surface. The Bulk API relation-updates page
is the full reference; rest-1.v1 equivalents are in
update examples.
// api/asset — set a single relation, adjust a multi relation
{
"from": "Story:1207",
"update": {
"Timebox": "Timebox:1010",
"Owners": { "add": ["Member:20"], "remove": ["Member:1035"] }
}
}
<!-- rest-1.v1 XML: same effect -->
<Asset>
<Relation name="Timebox" act="set"><Asset idref="Timebox:1010"/></Relation>
<Relation name="Owners"><Asset idref="Member:20" act="add"/><Asset idref="Member:1035" act="remove"/></Relation>
</Asset>
Deletes Cascade Down the Containment Tree
Deleting an asset soft-deletes what it contains — verified live: deleting a
Story deletes its child Tasks. The children are recoverable exactly like
their parent, but note the two-flag requirement to even see them: the
?deleted=1 URL parameter and IsDeleted: true in the query —
// POST /query.v1?deleted=1
{
"from": "Task",
"where": { "ID": "Task:3887", "IsDeleted": true },
"select": ["Name", "IsDeleted", "AssetState"]
}
Either flag alone returns nothing. See troubleshooting for this and related traps.
The Hierarchies at a Glance
The domain has several distinct hierarchies, and primary workitems (Story, Defect, TestSet) participate in two of them at once — through two different relation pairs:
Scope tree Portfolio (Super / Subs) Classification & breakdown (Parent / Children)
Scope Epic Theme
└─ Scope ├─ Epic (nesting) ├─ Theme (nesting)
└─ … └─ Story/Defect/TestSet └─ Story/Defect/TestSet
└─ Task, Test
Super/Subsis the portfolio hierarchy: an Epic can be theSuperof another Epic or of a primary workitem;Epic.Subs(typedWorkitem, read-only) therefore holds a mix of Epics and primary workitems.Parent/Childrenmeans different things by level. On a Task or Test,Parentis the owning primary workitem (breakdown). On a primary workitem,Parentis a Theme (classification into backlog groups) — and Themes nest via their ownParent.Theme.Children(typedWorkitem, read-only) holds Themes and primary workitems alike.
So a Story can simultaneously have a Super (its Epic), a Parent (its
Theme), and Children (its Tasks and Tests) — three hierarchies, one
asset. When traversing, downcast mixed collections
(Subs:Epic, Children:Task) to keep types straight.
Two scope rules ride along with these hierarchies:
- The portfolio hierarchy crosses project boundaries: an Epic and its
Subscan live in different Scopes. - Breakdown does not: a Task or Test is always in its parent's Scope
(
Task.Scopeis read-only). The two types differ in who may be that parent:Task.Parentis typedPrimaryWorkitem, so parenting a Task to an Epic is rejected with400 Wrong OID AssetType— whileTest.Parentis typedWorkitem, and parenting a Test to an Epic works (both verified live).
Newer portfolio-alignment types — Okr (with its KeyResult subtype) and
StrategicTheme — carry their own linkage vocabulary instead of
Parent/Super pairs: Okr.CascadedTo, Okr.AlignedWorkitems,
StrategicTheme.AlignedOkrs, StrategicTheme.Epics. Explore them through
meta.v1 if your instance uses OKRs.
For time-sliced hierarchy reporting, see reproducing reported numbers.
Patterns That Come up Constantly
Everything assigned to a member, across all work-item types — query the abstract type and filter by OID:
{
"from": "Workitem",
"filter": ["Owners='Member:20'", "AssetState!='Dead'"],
"select": ["Number", "Name", "Status.Name"],
"sort": ["-ChangeDateUTC"],
"page": { "size": 50, "start": 0 }
}
A project and everything beneath it — the Scope.ParentMeAndUp idiom
above.
Only the topmost Epic above a Story — SuperAndUp walks the full Epic
chain; a bracket filter for "has no Super of its own"
keeps just the root:
{
"from": "Story",
"where": { "Scope": "Scope:1308" },
"select": ["Number", "SuperAndUp[-Super].Number", "SuperAndUp[-Super].Name"],
"page": { "size": 50, "start": 0 }
}
Story with its epic and its tasks in one request — dotted paths upward, a nested subquery downward:
{
"from": "Story",
"where": { "ID": "Story:1207" },
"select": [
"Number", "Name", "Super.Number", "Super.Name",
{ "from": "Children:Task", "select": ["Number", "Name", "Status.Name"] }
]
}
Performance Notes
Every relation you project or filter through costs a join, and multi-value relations multiply rows. Project the minimum, filter on OIDs, and page — query size and performance covers the discipline in full.
Related Topics
- Attributes — the attribute side of the model
- Attribute paths — dots, downcasts, bracket filters
- Bulk API relation updates — add/remove semantics in full
- Moments and history — relations through time