---
id: relations
title: 'Relations in Agility'
description: 'Understanding relation attributes - the links that form the Agility asset graph - and how to traverse, filter and modify them'
sidebar_label: 'Relations'
sidebar_position: 3
tags: ['concepts', 'relations', 'data-model']
---

Relations are [attributes](/concepts/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:

```json
// 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](/concepts/attributes#selecting-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`](/apis/meta-v1).

## Traversing the Graph

Dots traverse to any depth, and each path arrives as its own flattened key:

```json
{
  "from": "Task",
  "where": { "ID": "Task:1469" },
  "select": ["Name", "Parent.Number", "Parent.Scope.Name"]
}
```

```json
[[{ "_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:

```json
{
  "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](/reference/query-grammar/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"*:

  ```json
  {
    "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](/apis/asset-bulk/relation-updates)
is the full reference; `rest-1.v1` equivalents are in
[update examples](/apis/rest-1/update-examples).

```json
// api/asset — set a single relation, adjust a multi relation
{
  "from": "Story:1207",
  "update": {
    "Timebox": "Timebox:1010",
    "Owners": { "add": ["Member:20"], "remove": ["Member:1035"] }
  }
}
```

```xml
<!-- 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 —

```json
// POST /query.v1?deleted=1
{
  "from": "Task",
  "where": { "ID": "Task:3887", "IsDeleted": true },
  "select": ["Name", "IsDeleted", "AssetState"]
}
```

Either flag alone returns nothing. See
[troubleshooting](/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:

```text
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](/guides/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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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](/guides/query-size-and-performance) covers the
discipline in full.

## Related Topics

- [Attributes](/concepts/attributes) — the attribute side of the model
- [Attribute paths](/reference/query-grammar/attribute-paths) — dots, downcasts, bracket filters
- [Bulk API relation updates](/apis/asset-bulk/relation-updates) — add/remove semantics in full
- [Moments and history](/concepts/moments-and-history) — relations through time
