# Manage Annotations

> MUST READ before creating, modifying, or querying annotations. Contains parameter names, coordinate model, and API quirks.

- Skill: `dylanroscover/manage-annotations` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dylanroscover/manage-annotations`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dylanroscover/manage-annotations/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: dylanroscover (https://skillmd.com/u/dylanroscover)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dylanroscover/manage-annotations

---

<!-- Generated by Embody/Envoy - Do not remove this comment -->

# Manage Annotations Workflow

## Creating Annotations

```
create_annotation(
    parent_path='/path/to/network',
    mode='annotate',     # 'annotate' (title bar), 'comment', or 'networkbox'
    title='My Group',
    text='Description of this group',
    x=..., y=..., width=..., height=...,
    color=[r, g, b],     # Optional, floats 0-1
    opacity=0.5           # Optional
)
```

**Always favor annotations over OP comments** for documenting operators or groups.

## Enclosing Operators

To create an annotation that encloses a group of operators:

1. **Get positions**: `get_network_layout` on the parent COMP (includes all operator positions and bounding box)
2. **Calculate bounding box**: Find `min_x`, `max_x`, `min_y`, `max_y` (max includes operator width/height: `max_x = max(op_x + op_w)`, `max_y = max(op_y + op_h)`)
3. **Add padding**: 70 units on left/right/bottom, **170 units on top** (title bar + body text)
4. **Set coordinates**:
   - `nodeX = min_x - 70`
   - `nodeY = min_y - 70` (BELOW operators, not above!)
   - `nodeWidth = max_x - min_x + 140`
   - `nodeHeight = max_y - min_y + 240` (70 bottom + 170 top)

## Coordinate Model

- `nodeX`/`nodeY` = **bottom-left corner**, width/height extend rightward and upward
- Title bar renders at the **top** of the rectangle
- **Common mistake**: Setting `nodeY` above the operators. `nodeY` must be BELOW (less than) the lowest operator's Y.

## Querying

- `get_annotations` - list all annotations in a COMP with properties and enclosed operators
- `get_enclosed_ops` - get operators enclosed by an annotation, or annotations enclosing an operator
- `set_annotation` - modify text, title, color, opacity, position, or size

## Deleting Annotations

**Delete via `delete_op`, never via raw `.destroy()` in `execute_python`.**
`delete_op` resolves utility annotations, purges any tracking, and arms an
auto-save checkpoint that re-exports the parent TDXN COMP's `.tdxn` without
the annotation -- the deletion is durable. A raw `.destroy()` leaves the
stale `annotations:` entry in the parent's `.tdxn` on disk, and the next
reimport of that COMP (import_network, manager Reload, or cold open)
resurrects the annotation with its pre-delete text.

## `annotateCOMP` Quirks

- **`utility` is `True` for every annotation** -- TD UI-drawn ones are born
  that way, `create_annotation` sets it, and TDXN import applies it on every
  annotation it recreates. (A bare Python `parent.create('annotateCOMP')`
  is `utility=False` -- set `ann.utility = True` immediately to match; a
  non-utility annotation is an ordinary COMP subtree that enumeration
  sweeps will walk into.)
- **`utility=True` hides the op from `op()`, `parent.op()`, AND
  `.children`** -- only `findChildren(includeUtility=True)` sees it, and a
  deep `findChildren` does not even DESCEND into a utility annotate's
  subtree unless `includeUtility=True` is passed (verified live, TD
  2025.33070). Paths THROUGH a utility annotate to its interior ops still
  resolve via `op()`.
- **Every Envoy op-path tool resolves utility annotations** (`delete_op`,
  `set_parameter`, `set_op_position`, `get_op`, ... -- they share one
  utility-aware resolver). For DISCOVERY, use `get_annotations` (always
  sees them) or pass `include_utility=True` to
  `query_network`/`find_children` -- with the default `False`, annotations
  are invisible in those listings.
- **Annotations are never externalized per-op** -- they round-trip through
  the parent TDXN COMP's semantic `annotations:` section. `externalize_op`
  refuses them, and tagging sweeps skip them and their internals (the
  widget internals are TD-managed stock content cloned from TDAnnotate).
- **`envoy_bot_*` is reserved for Embot**, the mascot Envoy stands on the
  operator it is working on (the `Embot` parameter). The read tools hide
  those parts and report how many as `embot_hidden`; Embody strips them
  from every saved file and deletes loose ones on save. Never create,
  move, edit or delete an annotation with that prefix -- and never name
  one of yours that way, or it will be deleted as an artifact.
- `.type` returns `'annotate'` (not `'annotateCOMP'`)
- `findChildren(type=annotateCOMP)` requires the class object, not the string
- Cannot be reliably renamed after creation (TD also ignores a name passed
  at create time -- rename right after creating instead)

