Skip to main content

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​

RelationValueOnMeaning
Scopesinglework itemsThe containing project
Statussinglework itemsWorkflow list value
Prioritysingleprimary workitemsPriority list value
Teamsingleprimary workitemsOwning team
Timeboxsingleprimary workitemsSprint / iteration
Parentsinglesee belowTask/Test → owning primary workitem; Story/Defect/TestSet → owning Theme; Theme → parent Theme
Supersingleprimary workitems, EpicsThe owning Epic; Epics nest via Super/Subs
Ownersmultiwork itemsAssigned members
Childrenmultiprimary workitemsOwned Tasks and Tests
Dependenciesmultiprimary workitemsUpstream items this one depends on
Dependantsmultiprimary workitemsDownstream items that depend on this one
AffectedByDefectsmultiprimary workitemsDefects 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.Scope is 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 / Subs is the portfolio hierarchy: an Epic can be the Super of another Epic or of a primary workitem; Epic.Subs (typed Workitem, read-only) therefore holds a mix of Epics and primary workitems.
  • Parent / Children means different things by level. On a Task or Test, Parent is the owning primary workitem (breakdown). On a primary workitem, Parent is a Theme (classification into backlog groups) — and Themes nest via their own Parent. Theme.Children (typed Workitem, 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 Subs can live in different Scopes.
  • Breakdown does not: a Task or Test is always in its parent's Scope (Task.Scope is read-only). The two types differ in who may be that parent: Task.Parent is typed PrimaryWorkitem, so parenting a Task to an Epic is rejected with 400 Wrong OID AssetType — while Test.Parent is typed Workitem, 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.