agentleFS
dylanroscoverdylanroscover/Embody/.claude/skills/create-operator/SKILL.md

create-operator

Skill213 starsChanged 2 months agoMITView on GitHub

MUST READ before creating or moving any operator (create_op, copy_op, set_op_position, or execute_python builds). Required verification, canonical positioning recipe, and error-checking steps.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 4 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Follow these steps every time you create operators via MCP:

    line 9
  2. Very specific:

    NEVER: create under /local or /local/* -- volatile storage, not saved with the .toe.

    line 12
  3. Very specific:

    Scan existing layout: Use get_network_layout on the parent COMP.

    line 19
  4. Very specific:

    Set OP-reference parameters with relative paths: If the operator has parameters referencing other operators (Camera, Geometry, Lights, TOP, …

    line 25
  5. Very specific:

    Verify layout: Call get_network_layout again.

    line 26
  6. Very specific:

    Batch-compute ALL positions: before placing anything; after placing, get_network_layout to verify.

    line 49
  7. Distinctive:

    Every operator inside exactly one annotation.

    line 81
  8. Distinctive:

    NEVER skip positioning - auto-placement produces unreadable networks

    line 95
  9. Distinctive:

    NEVER place an operator on top of another operator - always scan first

    line 96
  10. Very specific:

    Never rely on layout() for production networks

    line 97

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- Generated by Embody/Envoy - Do not remove this comment - sha:8f0ca97bbdf8c00f -->

Create Operator Workflow

Follow these steps every time you create operators via MCP:

  1. Choose the correct parent network -- ASSOCIATE with where the user already works. Inconsistent placement is the #1 scoping bug (/ one run, /project1 the next). The fix is to anchor on the user's own structure, which is stable across runs -- not on a transient pane:
    • NEVER create under /local or /local/* -- volatile storage, not saved with the .toe.
    • If the user named a target, use it.
    • Default home = the container that holds the Embody COMP. execute_python with result = op.Embody.parent().path. Embody in /project1 -> build in /project1; Embody at the root / -> build at /. It returns the SAME home every run -- this is the consistency mechanism, and it is the level the user chose by placing Embody there.
    • Override only for deliberate navigation. If the user has actively opened a specific content network to work in, build there instead (ui.panes.current.owner.path) -- UNLESS that pane is sitting at the bare root / (a non-deliberate default, e.g. right after the project opens), in which case ignore it and use the Embody home above. Never treat bare / as a content home.
    • Discover, never guess. Confirm the real container names with query_network on / -- never hardcode /project1 (it may be renamed, and there may be more than one).
    • Keep one task's COMPs together under the SAME parent, grouped in one container / annotation. Don't split related COMPs across levels.
  2. Discover the target network: query_network on the target parent to confirm it exists and see existing operators
  3. Scan existing layout: Use get_network_layout on the parent COMP. Note each operator's nodeX, nodeY, nodeWidth, and nodeHeight - operators vary in size (100-300+ units wide)
  4. Plan positions BEFORE creating: Batch-compute grid-aligned positions for ALL operators you intend to create. Signal flow is left-to-right: inputs on the left, outputs on the right. Supporting operators (DATs feeding a TOP, CHOPs feeding parameters) go to the left of or below the operator they feed. Snap all coordinates to the 200-unit grid. See the Positioning Rules section below.
  5. Create each operator: create_op with the desired type and name
  6. Position each operator: set_op_position to place it at the pre-computed grid position. Auto-placement is NOT acceptable - it produces messy, unreadable networks. You MUST explicitly position every operator you create. Moving a host via set_op_position carries its docked companions along (re-hugged below the new spot, reported as docks_moved), so position the host FIRST and any deliberately-placed dock after.
  7. Docked companions (callback/shader/info DATs): create_op, copy_op, and set_op_position auto-hug every docked op in a tight row ~30 units below its host (docks_placed / docks_moved in the result) - do not re-plan grid slots for them. Ops created inside execute_python get NO such placement at create time (Envoy only auto-hugs badly scattered docks after the call, with a LAYOUT WARNING); if you create dock-spawning ops (GLSL TOP/MAT, execute DATs, OSC in/out) in a script, place their docks yourself per the Docked companion layout formula below.
  8. Connect: connect_ops to wire inputs/outputs. Wires must flow left-to-right (positive X). If a wire would go backward, the downstream op is misplaced - reposition it.
  9. Set OP-reference parameters with relative paths: If the operator has parameters referencing other operators (Camera, Geometry, Lights, TOP, CHOP, etc.), use sibling names (cam) or relative paths (../shared/lut) - NEVER absolute paths (/project1/scene/cam). See parameters.md section OP-Reference Parameter Values.
  10. Verify layout: Call get_network_layout again. Confirm no overlaps, grid alignment is intact, signal flows left-to-right, and every entry carrying dockedTo sits in a tight row just below its named host (docked ops are the one exception to 200-grid spacing - they hug).
  11. Verify errors: get_op_errors with recurse=true to check for errors and warnings. Fix all errors before considering the task complete
  12. Visual verification: For any renderable result (a TOP chain or a render), capture the output TOP with capture_top and confirm it actually renders (not black) and matches intent; for a 3D render, confirm a camera, a light, and geometry display/render flags are present. For anything but a trivial op, load the /visual-aesthetics skill to judge composition/value/color/contrast.

Operator Type Preferences

  • Prefer POPs over SOPs for geometry and particle work. POPs (Point Operators) are GPU-accelerated and significantly more performant than SOPs (Surface operators). Only use SOPs when POP equivalents don't exist or when CPU-side geometry manipulation is specifically required.

Geometry COMP: delete the default torus

A freshly created geometryCOMP ships with a default torus1 SOP inside, render flag ON. The moment you add your OWN geometry to that COMP -- a SOP chain, a POP chain terminating in a nullPOP, or imported geometry -- delete torus1 (or turn OFF its render flag). It is the RENDER flag (purple) that selects a SOP for the Render TOP, so leaving it on draws BOTH: your geometry AND a phantom torus. It is easy to miss: adding your own SOP auto-clears the torus's DISPLAY flag (blue, exclusive per viewer) so the SOP / geometry viewer looks correct, while its RENDER flag (non-exclusive) stays on and keeps drawing it in the render output. Applies to EVERY geometryCOMP you populate, not just POP builds. (TDXN import already strips these auto-created defaults, so this bites only live create_op builds -- which is why it shows up in some projects and not others.)

Positioning Rules (canonical layout recipe)

  • Every operator MUST be explicitly positioned on the 200-unit grid. Do NOT rely on auto-placement.
  • Spacing is size + gap, BOTH axes, ALWAYS -- never a fixed step. This is the #1 overlap bug: stepping by a constant (e.g. nodeY - 100, nodeX + 300) while the tiles are bigger than the step (a 134-tall node stepped by 100 overlaps by 34). The step MUST be computed from the ACTUAL node extent on that axis plus a gap, then snapped up to the grid:
    • Horizontal: next_x = prev_nodeX + prev_nodeWidth + gap (gap >= 200), rounded up to the next 200 multiple.
    • Vertical: next_y = prev_nodeY - (prev_nodeHeight + gap), rounded so the step clears the tile. For a stack of same-size tiles: step = ceil((maxNodeHeight + gap) / 200) * 200 and place tile i at y = -i * step. A 134-tall stack -> step = 200 (66 gap); a 260-tall stack -> step = 400. Bigger nodes => bigger spacing, mechanically.
  • The grid is the SNAP target, not the step size -- when a node is taller/wider than 200, the step is 400/600/... (the next multiple that clears size + gap), NEVER a flat 200. 400 units vertical minimum between parallel chains or annotation groups (widen when tiles are tall). Y increases upward; new rows go downward.
  • Use actual dimensions, not assumptions. COMPs (~160+ wide) and panel widgets (often 130-160 wide x 130+ tall) are much bigger than a bare TOP (~120 sq) -- a step that clears one overlaps the other.
  • Signal flow is left-to-right; wires must flow forward (positive X) -- never a backward "S" wire. Every operator must have a higher nodeX than each operator feeding it. The classic mistake: placing a source at the SAME nodeX as its destination (directly above or below) -- the source's output then sits right of the dest's input and the wire loops back. Place each source so its right edge (nodeX + nodeWidth) is left of the destination's nodeX. If a wire bends backward, move the downstream op right or the source left.
  • Branches split vertically, each continuing left-to-right; minimize crossings. Same row = same stage in a chain (same X); same column = same function across parallel chains (same Y).
  • New operators extend a group to the RIGHT (rightmost_nodeX + rightmost_nodeWidth + 200, snapped); a parallel chain goes DOWN (lowest Y in group - 400). Don't fill a gap mid-row of a finished chain.
  • Batch-compute ALL positions before placing anything; after placing, get_network_layout to verify.

Related operators stay close

Operators that reference each other belong near each other, even with no wire or dock: a MAT beside the Geometry COMP it shades; a camera and light(s) near their Render TOP; a CHOP/DAT that drives parameters by reference near the op it drives; any COMP named in an op() call, expression, or material slot near the op that references it. Rule of thumb: if op A names op B, a reader should see both without scrolling.

Panel-COMP widgets stack too

Buttons/fields/containers inside an align-driven panel (Container/Button laid out verttb/horizlr) render by their align + order pars, so their nodeX/nodeY are cosmetic to the UI -- BUT they still get network tiles, and a verttb container full of 134-tall buttons stepped by 100 overlaps in the editor exactly like data-flow ops do. Apply the same size + gap stepping to panel-widget tiles (stack by panel order); it never affects the rendered panel. This is the one layout class the LAYOUT WARNING lint does NOT police -- it is on you to tidy.

Docked companion layout formula

Hosts that dock: execute/callback DATs (chopExecuteDAT, datExecuteDAT, panelExecuteDAT, parameterExecuteDAT, executeDAT); input DATs (keyboardinDAT, mouseinDAT, oscinDAT/oscoutDAT); and GLSL ops -- a glslTOP/glslmultiTOP docks a pixel DAT, a compute DAT, AND an info DAT (the multi variant also docks a vertex DAT); a glslMAT docks a vertex DAT, a pixel DAT, AND an info DAT (no compute DAT); a glslPOP docks a compute DAT and an info DAT.

Given host bottom-left (sx,sy), size (sw,sh), and N docks (use max dw,dh across docks). Docks HUG the host -- the one deliberate exception to 200-grid spacing:

  • Row Y: row_y = sy - dh - 30 -- a tight ~30-unit gap below the host's bottom edge, NOT a full grid step.
  • Slot step: step = dw + 20 -- one compact cluster under the host.
  • Center the row: dock i at nodeX = (sx + sw/2) + (i - (N-1)/2) * step - dw/2, filling per this table; overflow stacks into a second tight row (row_y - dh - 30) rather than widening:
NPattern
1[C]
2[C, R]
3[L, C, R]
4[L, C, R, R2]
5[L2, L, C, R, R2]

If another op occupies a dock's slot, move that other op -- docked ops take priority, are never threaded around obstacles, never overlap, never stranded. For execute_python builds: query [d.path for d in op('PATH').docked], reposition EVERY dock per the formula, then get_network_layout to confirm. A glslTOP built inside execute_python is the classic trap.

Annotations and complexity

  • Every operator inside exactly one annotation; no orphans; annotations never overlap (>= 400 units between edges); expand the annotation after every addition. Title names the FUNCTION, not the implementation ("Audio Mixing", not "CHOP chain 2").
  • MUST invoke /manage-annotations before create_annotation or set_annotation -- nodeX/nodeY is the bottom-left corner.
  • 4-5 annotation groups in one network: consider baseCOMPs (containerCOMPs for UI). 15-20 operators in one group: consider encapsulating into a COMP that replaces the group, moving the annotation title to the COMP name/label.

Anti-patterns

  • Leaving execute_python-created ops at (0, 0) / overlapping -- Envoy emits a LAYOUT WARNING, but position + verify regardless.
  • Fixed offsets like nodeX + 300 without nodeWidth -- the #1 cause of overlaps.
  • TD's COMP.layout() -- overlapping, unreadable results.
  • Creating operators without updating the enclosing annotation; set_op_position into an unverified slot (it has no overlap detection).
  • Leaving docked callback/info DATs at their auto-spawn position after a script build.

Key Reminders

  • NEVER skip positioning - auto-placement produces unreadable networks
  • NEVER place an operator on top of another operator - always scan first
  • Never rely on layout() for production networks
  • New operators go near related operators, not at origin
  • Docked companions hug their host (~30 units below, never a full grid step away). The MCP tools enforce this; after any execute_python build, check every dockedTo entry in get_network_layout yourself
  • For current network location: execute_python with result = ui.panes.current.owner.path
  • Placing a COMP: anchor on where the user put Embody (op.Embody.parent().path) for a consistent home; override only when the user has deliberately navigated into a content network (never bare /); discover, never guess, /project1; keep one task's COMPs in one home
  • New geometryCOMP: delete its default torus1 the moment you add your own geometry, or it renders a phantom torus behind your scene

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.