---
id: working-with-comments
title: 'Working with Comments'
description: 'Conversations, Expressions and Mentions - the model behind comments, and how to create, reply to and read them through the API'
sidebar_label: 'Working with Comments'
sidebar_position: 6
tags: ['guides', 'comments', 'conversations']
---

What the UI calls a **comment** is an `Expression` asset inside a
`Conversation` — and "commenting on" a workitem is not a special
relationship: it is a **mention**.

## The Model

| Type           | Role                                                                     |
| -------------- | ------------------------------------------------------------------------ |
| `Conversation` | The thread container. Has `Join`/`Leave` operations — and **no `Delete`**: conversations cannot be removed through the API. |
| `Expression`   | One comment. `Author` and `Content` are required; `AuthoredAt` is marked required but the server fills it in when omitted. |

The relations that make it work:

- **`Expression.BelongsTo`** → its Conversation.
- **`Expression.InReplyTo`** → the Expression it replies to
  (`Replies` is the read-only reverse).
- **`Expression.Mentions`** → any assets the comment references — typed
  `BaseAsset`, so workitems, members, projects, anything. **Mentioning an
  asset is what places the comment in that asset's conversation stream.**
  The reverse is `MentionedInExpressions`, available on every asset.

```mermaid
flowchart LR
    E1[Expression<br/>the comment] -- BelongsTo --> C[Conversation<br/>the thread]
    E2[Expression<br/>a reply] -- InReplyTo --> E1
    E1 -- "Mentions (anchors the comment<br/>to the asset's stream)" --> A[Any BaseAsset:<br/>Story, Member, Scope, …]
```

One rule the server enforces: an Expression must have **either** a
`BelongsTo` conversation **or** an `InReplyTo` parent. Creating one with
neither fails with
`Violation'AssignExpressionConversation'ConversationOrInReplyToRequired` —
a top-level comment needs a thread; a reply inherits its parent's.

## Comment on a Workitem (with a Cross-Reference and a Reply)

One Bulk API request can create the thread, the comment and a reply —
aliases carry the links, including `InReplyTo` referencing the sibling
command:

```json
[
  { "@conv": { "AssetType": "Conversation" } },
  {
    "@comment": {
      "AssetType": "Expression",
      "BelongsTo": "@conv",
      "Author": "Member:20",
      "Content": "This duplicates the login work — see S-01002.",
      "Mentions": ["Story:1207", "Story:1208"]
    }
  },
  {
    "AssetType": "Expression",
    "Author": "Member:20",
    "Content": "Agreed — closing this one.",
    "InReplyTo": "@comment"
  }
]
```

The first entry in `Mentions` anchors the comment to the workitem being
commented on; the second is the cross-reference. The reply carries no
`Mentions` — and therefore does **not** appear in either story's stream,
only in the thread.

## Starting from Numbers

A create cannot resolve a `Number` inline
([alias limitations](/apis/asset-bulk/aliases-and-projection)) — resolve
first, then create:

```json
[
  { "from": "Story", "filter": ["Number=$A"], "select": ["Number"], "with": { "$A": "S-01001" } },
  { "from": "Story", "filter": ["Number=$B"], "select": ["Number"], "with": { "$B": "S-01002" } }
]
```

Each result's `_oid` feeds the `Mentions` array above.

## Reading Comments

From the asset's side — every asset carries `MentionedInExpressions`:

```json
{
  "from": "Story",
  "where": { "ID": "Story:1207" },
  "select": [{ "from": "MentionedInExpressions", "select": ["Content", "Author.Name", "AuthoredAt"] }]
}
```

Or from the comment side, filtering the mention relation by OID:

```json
{
  "from": "Expression",
  "filter": ["Mentions='Story:1207'"],
  "select": ["Content", "Author.Name", "AuthoredAt", "InReplyTo"],
  "sort": ["AuthoredAt"],
  "page": { "size": 50, "start": 0 }
}
```

Remember the reply nuance: replies without their own mentions appear only
through the thread (`InReplyTo`/`Replies`), not in the asset-side view.

## Deleting

Expressions delete normally (`?op=Delete`, recoverable like everything
else). Conversations do not — an emptied thread simply remains. Plan
accordingly if your integration creates threads programmatically.

## Related Topics

- [Asset permalinks](/apis/asset-permalinks) — include an
  `assetdetail.v1` link with the mention so the notified member can click
  straight to the asset.

- Product feature: [Using Conversations](https://docs.digital.ai/agility/docs/agility/using-conversations) — what
  the comments this guide writes look like to users.

- [Bulk API aliases](/apis/asset-bulk/aliases-and-projection)
- [Relations](/concepts/relations) — mentions are ordinary relations
- [Operations](/concepts/operations) — the delete/recovery model
