ServicesComment

Comment

Universal threaded comments that attach to any resource by a typed reference: nested reply trees, a fixed reaction set, moderation states with an audit trail, anonymous and spoiler flags, soft deletion that preserves thread structure, @mention extraction that lands in Inbox, and hidden agent-audit notes. Every example on this page was executed against a live server.

When to reach for it: feedback on timeline events, items, worlds, or support cases; nested discussions with reactions; moderated community threads; hidden technical notes agents attach to resources.

When not to: chat-style realtime messaging belongs to Pub/Sub; persistent per-user notifications belong to Inbox (Comment already pushes mention and reply notifications there for you).

Concepts

  • Targets and threads - a comment attaches to a {type, id} target reference, like item / docs4-item-1. All comments on one target share a thread id of the form thr_{type}_{id}. The target is not validated against the owning service; any type/id pair opens a thread.
  • Replies form a tree - a reply carries parent_comment_id, a depth (0 for top-level), and a materialized path of ancestor ids ending in itself. Nesting is capped at depth 10 by default.
  • Moderation states - pending, visible, flagged, hidden, removed, deleted. New comments start in pending (automatic toxicity scoring is not yet wired) and still appear in threads; hidden and removed are tombstoned for regular viewers. See Moderation and visibility.
  • Reactions are a fixed set - like, insightful, laugh, question, celebrate; anything else is 400 invalid_reaction. Counts are per-comment, and each viewer sees their own reactions in viewer_reactions.
  • Anonymous and spoiler flags - an anonymous comment hides author from other users (served as null) while moderators and the author still see it. spoiler is carried on the body for clients to blur.
  • Mentions notify - @identifier tokens are extracted (deduplicated, alphanumeric plus _ and -), returned in mentions, and delivered as Inbox notifications to the mentioned users; replies notify the parent's author the same way.

Post, reply, read

snug comment post --target docs4-item-1 --type item \
  --body "First pass looks good, @docs4-other should confirm."
snug comment reply --parent hvTsEKZZQmqhxTfuYxDP --target docs4-item-1 \
  --type item --body "Confirmed, the fix holds."

snug --output json comment thread --target docs4-item-1 --type item
snug --output json comment get --comment-id hvTsEKZZQmqhxTfuYxDP

post also takes --spoiler, --anonymous, and --idempotency-key; thread pages with --page/--page-size and takes --max-depth, --sort-order asc|desc (by created_at), and --pinned-first. Over HTTP, creation is one endpoint - a reply is just a create with parent_comment_id:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target": {"type": "item", "id": "docs4-item-1"},
       "body": {"text": "Shipping this today."}}' \
  http://localhost:4000/api/v1/comments
{
  "status": 201,
  "msg": "Created",
  "data": {
    "comment_id": "yxjegyesTDVYJqSPvhYz",
    "thread_id": "thr_item_docs4-item-1",
    "moderation_state": "pending",
    "mentions": [],
    "created_at": "2026-08-28T05:02:38.060063Z"
  }
}

A comment in a thread or get response carries the full record: body, author, depth and path, moderation_state, pinned, reaction_counts, and viewer_reactions. Bodies are markdown by default (plain is the alternative), up to 10,000 characters - a blank or oversized body is rejected with 400.

Passing an idempotency_key on create makes retries safe: repeating the same key returns the original comment_id instead of posting a duplicate (verified live; the dedup window is 24 hours).

Reactions

snug comment react --comment-id hvTsEKZZQmqhxTfuYxDP --reaction like
snug comment unreact --comment-id hvTsEKZZQmqhxTfuYxDP --reaction like
snug comment toggle --comment-id hvTsEKZZQmqhxTfuYxDP --reaction insightful

toggle adds the reaction if absent and removes it if present, returning reacted plus the updated reaction_counts - convenient for a reaction button. Reaction names are case-insensitive (INSIGHTFUL counts as insightful), and one actor can hold several distinct reactions on the same comment, up to a configured cap.

Edit and delete

snug comment edit --comment-id hvTsEKZZQmqhxTfuYxDP \
  --body "First pass looks good, @docs4-other and @docs4-third please confirm."
snug comment delete --comment-id hvTsEKZZQmqhxTfuYxDP

Only the author can edit - anyone else, moderators included, gets 403 insufficient_permissions. An edit re-extracts mentions and notifies only newly added ones. Deletion is available to the author or a moderator and is a soft delete: the node stays in the tree with moderation_state: "deleted", deleted_at set, the body redacted to [deleted], and mentions cleared, so replies below it keep their context.

Deleted is terminal - all three reproduced live:

  • editing a deleted comment fails with 409 comment_deleted,
  • replying under a deleted parent fails with 409 comment_deleted,
  • moderating it fails with 400 invalid_moderation_transition.

Moderation, pinning, agent audits

Moderator-gated operations - state transitions, the flagged queue, pinning, hidden-comment visibility, and agent-audit notes - have their own page: Moderation and visibility.

Limits and configuration

Body length (10,000 chars), reply depth (10), distinct reactions per actor (5), thread page size cap (200), and the auto-flag toxicity threshold are tunable via the COMMENT_* variables in the Comment CONFIG reference.

Reference

  • Comment API - every endpoint, callable
  • Related: Inbox receives mention and reply notifications, Pub/Sub for chat-style realtime messaging

On this page