Envoy MCP Tool Reference
Mutating TD-authoring operations are wrapped in TD undo blocks (one batch_operations call = one Ctrl+Z step); read-only tools, run_tests, cook_op, and disk-only ops (export_network, save_externalization) are not.
Operator Management
| Tool |
Parameters |
Description |
create_op |
parent_path, op_type, name? |
Create a new operator (e.g., baseCOMP, noiseTOP, textDAT, gridPOP). Auto-positions it and hugs any docked companions below it (docks_placed) |
create_extension |
parent_path, class_name, name?, code?, promote?, ext_name?, ext_index?, existing_comp?, parent_shortcut? |
Create a TD extension: baseCOMP + text DAT + extension wiring |
delete_op |
op_path, override? |
Delete an operator (multi-session gated; override bypasses, say so) |
copy_op |
source_path, dest_parent, new_name? |
Copy operator to new location. Auto-positions the copy and hugs its docked companions (docks_placed) |
rename_op |
op_path, new_name |
Rename an operator |
get_op |
op_path, include_defaults? |
Returns NON-DEFAULT parameters by default (include_defaults=True for all); parameter-heavy COMPs are expensive (~3k+ tokens full) -- prefer read_tdxn for structure reads |
query_network |
parent_path?, recursive?, op_type?, include_utility? |
Compact operator list: path/type/family/depth; name = last path segment. Set include_utility=True to include annotations |
find_children |
op_path, name?, type?, depth?, tags?, text?, comment?, include_utility? |
Advanced search using TD's findChildren |
cook_op |
op_path, force?, recurse? |
Force-cook an operator |
Parameter Control
| Tool |
Parameters |
Description |
set_parameter |
op_path, par_name, value?, mode?, expr?, bind_expr? |
Set value, expression, bind expression, or mode; rejects invalid Menu values with valid menuNames; sequence-block names auto-grow (const5name -> 6 blocks) |
get_parameter |
op_path, par_name?, search?, search_in?, depth?, max_results?, details? |
Single-parameter mode is compact by default (path, parameter, value, mode, label, mode-specific refs, menuNames); details=True restores default/style/range/menuLabels/menuIndex. Search ignores details |
DAT Content
| Tool |
Parameters |
Description |
get_dat_content |
op_path, format? |
Get DAT text or table data ("text", "table", "auto"), or "stats" to reduce a table to per-column stats + head/tail rows |
set_dat_content |
op_path, text?, rows?, clear?, confirm_wipe? |
Full-replace DAT content. Refuses no-action calls and wipes (text="", rows=[], or clear=True with no content) unless confirm_wipe=True. For partial text edits prefer edit_dat_content; use this for tables, full rewrites, or intentional wipes. |
edit_dat_content |
op_path, old_string, new_string, replace_all?, confirm_wipe? |
Surgical text edit on a text DAT. old_string must appear exactly once by default; widen with context or pass replace_all=True. Refuses empty/identical strings and wipes unless confirm_wipe=True; not-found errors include diagnostics. Tables go through set_dat_content(rows=...). |
CHOP / POP Data
Reduced reads -- never a blind dump. A 4x600 CHOP is 2,400 raw floats; these
return the shape and range instead.
| Tool |
Parameters |
Description |
get_chop_data |
op_path, channels?, samples?, compare_to? |
Per-channel min/max/mean/std/first/last, capped at 32 channels. channels is a name glob; samples>0 adds head/tail raw values. compare_to another CHOP adds a diff of per-channel deltas -- the "what did this chain do to my data" read |
get_pop_data |
op_path, attributes?, samples?, max_points? |
Point/prim/vert attribute metadata (name, size, type). Metadata costs 0.02ms at ANY point count; reading point VALUES is a GPU->CPU readback (9.5ms at 16k points, ~69ms at 160k -- about 4 frames at 60fps), so samples>0 is opt-in and refused above max_points (default 50,000) |
Why the POP guard is on point count, not sample count: POP.points(attr, startIndex, count) accepts both slicing arguments and ignores them (verified on 2025.33070) -- the readback happens regardless. Asking for 4,096 points from a 160k POP measured slower than reading all of them. Reach for samples only when you actually need values, and prefer a smaller upstream POP for inspection.
Operator Flags
| Tool |
Parameters |
Description |
get_op_flags |
op_path |
Get all flags |
set_op_flags |
op_path, bypass?, lock?, display?, render?, viewer?, current?, expose?, allowCooking?, selected?, cloneImmune?, componentCloneImmune?, showCustomOnly?, showDocked? |
Set one or more flags. The last four are the authored flags TDXN round-trips; a flag the operator cannot carry (e.g. componentCloneImmune on a TOP) comes back in unsupported_flags rather than being silently dropped |
Operator Positioning & Layout
| Tool |
Parameters |
Description |
get_op_position |
op_path |
Get position, size, color, and comment |
get_network_layout |
comp_path, include_annotations? |
Compact layout for all children in a COMP: path/type/nodeX/nodeY/nodeWidth/nodeHeight plus bounding_box; docked ops carry dockedTo (host name). Centers are nodeX+nodeWidth/2; annotation text is capped at 160 chars. Use instead of repeated get_op_position calls |
set_op_position |
op_path, x?, y?, width?, height?, color?, comment? |
Set position, size, color ([r,g,b] 0-1), or comment. Moving a host re-hugs its docked companions below the new spot (docks_moved); position the host before any explicitly-placed dock |
layout_children |
op_path |
Auto-layout all children in a COMP |
Annotations
| Tool |
Parameters |
Description |
create_annotation |
parent_path, mode?, text?, title?, x?, y?, width?, height?, color?, opacity?, name? |
Create annotation ("annotate", "comment", "networkbox") |
get_annotations |
parent_path |
List all annotations with properties and enclosed operators |
set_annotation |
op_path, text?, title?, color?, opacity?, width?, height?, x?, y? |
Modify annotation properties |
get_enclosed_ops |
op_path |
Get ops enclosed by annotation, or annotations enclosing an op |
Performance Monitoring
| Tool |
Parameters |
Description |
get_op_performance |
op_path, include_children? |
Get CPU/GPU cook times, memory, cook counts |
get_project_performance |
include_hotspots? |
Get project-level FPS, frame time, GPU/CPU memory, dropped frames, active ops, GPU temp. Optional hotspot ranking of top N COMPs by cook time |
Connections
| Tool |
Parameters |
Description |
connect_ops |
source_path, dest_path, source_index?, dest_index?, comp? |
Wire two operators. comp=True for COMP connectors |
disconnect_op |
op_path, input_index?, comp? |
Disconnect an input |
get_connections |
op_path |
Get all input/output connections |
Code Execution
| Tool |
Parameters |
Description |
execute_python |
code |
Execute Python in TD. Set result variable to return values |
Envoy's own host: execute_python refuses (nothing runs; error_code envoy.embody.host_destroy_refused) code that would destroy or reload the Embody COMP, an ancestor, / or Envoy's extension DAT in the same call -- me and parent() ARE the Embody COMP here. delete_op, destroy-class exec_op_method and reload pulses via set_parameter refuse the same targets. Stop and ask the user; only when they asked to move or remove Embody, follow the refusal text (one deferred run() string).
Introspection & Diagnostics
| Tool |
Parameters |
Description |
get_td_info |
(none) |
TD version, build, OS, Envoy version |
get_op_errors |
op_path, recurse? |
Errors and warnings for op and children. Covers cook errors, Python tracebacks (kind: "script" entries -- invisible to op.errors()), and GLSL compile failures (shaderErrors) |
exec_op_method |
op_path, method, args?, kwargs? |
Call a method on an operator |
get_td_classes |
(none) |
List all Python classes in td module |
get_td_class_details |
class_name |
Get methods, properties, docs for a TD class |
get_module_help |
module_name |
Python help text for a module |
get_docs |
query, section?, source?, max_chars? |
Look up official TD docs; offline mirror preferred, docs.derivative.ca fallback; normal responses return title, source, sections_available, content; ambiguous offline lookups return matches instead of a page |
get_guidance |
topic? |
Read THIS project's checked-in TouchDesigner doctrine (.claude/rules/*.md, .claude/skills/*/SKILL.md). Bare call lists topics; topic returns one document. Matching ignores case/punctuation. Answered worker-side (no TD round-trip). Complements get_docs: get_docs is official Derivative documentation, get_guidance is how this project wants the work done -- and it is the only way clients with no skills folder (VS Code, Copilot, Windsurf) see them; Codex, Cursor, Gemini and Antigravity read .agents/skills/, OpenCode reads .claude/skills/ |
get_focus |
(none) |
What the user is looking at: network (current pane), selected, current, rollover, plus target/targetSource and a note. Call it to resolve "this operator" / "fix this" before guessing a path. DISAMBIGUATION: "this" means the SELECTED/current op, NEVER the rollover (rollover is incidental mouse position). Headless TD returns headless: true with nulls |
get_sessions |
(none) |
List AI client sessions connected to this Envoy (sid, label, pid, idle, last tool, recent_scopes = op paths/files recently modified, claims = scopes held, stale flag) plus you = caller's own sid. Check at session start and before large or destructive operations so concurrent sessions don't clobber each other. Response may include worktrees: in-flight durable worktree tasks, visible even after their session ended |
claim_scope |
scope, note?, ttl? |
Cooperative WRITE lease on an op-path prefix, file:<repo-relative> path, or project:<name> scope. Peers' overlapping claims are refused while yours is live; their destructive ops on it are gated. Auto-renews on your own writes; expires on TTL or session silence. project:worktree-* claims are DURABLE: they survive session death and Envoy restarts, expiring when the worktree directory is removed (7-day backstop) |
release_scope |
scope |
Release a lease you hold (polite; expiry also handles it) |
announce_task |
title, scopes?, note? |
Announce a unit of work to the shared task ledger (.embody/tasks.json) so parallel sessions see what is being worked on and what is FINISHED but uncommitted. Announce at the start of substantive work (a feature, fix, refactor); keep it honest with update_task. Active entries ride on get_sessions for every session |
update_task |
task_id, status?, note?, commit? |
Transition a ledger task: done_uncommitted when the work is finished but sitting uncommitted in the tree (the state peers MUST see), committed with the sha once it lands (a sha alone implies the transition), abandoned when dropped. Any session may update any task -- non-owner writes record updated_by |
preflight_landing |
worktree_path |
Landing safety check for a worktree diff -- intersects the files it would land with main-tree dirt, peer file: claims/touches, and unsaved live TDXN state (runtime dirty state; legacy tsv dirty columns still honored). Run BEFORE porting any worktree diff; verdict conflicts means reconcile first |
MCP Prompts
| Prompt |
Parameters |
Description |
search_op |
op_name, op_type? |
Guide for searching operators |
check_op_errors |
op_path |
Guide for inspecting/resolving errors and warnings |
connect_ops |
(none) |
Guide for wiring operators |
create_extension_guide |
(none) |
Guide for creating extensions |
Embody Integration
| Tool |
Parameters |
Description |
externalize_op |
op_path, tag_type? |
Tag and externalize operator to disk (one step) |
remove_externalization_tag |
op_path, delete_file? |
Remove externalization tracking (tag + row + TDXN breadcrumb); delete_file=True also deletes the file (best-effort). Returns removed_tags, removed_rows, removed_anything, summary -- an operator can have a tracked row but NO tag, so check removed_anything, not removed_tags, to confirm cleanup |
get_externalizations |
(none) |
List all externalized operators |
save_externalization |
op_path |
Force re-export an already-externalized operator |
get_externalization_status |
op_path |
Get dirty state, build, timestamp, path |
TDXN Network Format
| Tool |
Parameters |
Description |
read_tdxn |
comp_path?, include_dat_content?, max_depth?, embed_all? |
Preferred for reading >=3 operators. Returns live network as a TDXN dict. ~20-90x fewer tokens than get_op+query_network walks thanks to default-omission, type_defaults, and par_templates. |
export_network |
root_path?, include_dat_content?, output_file?, max_depth?, embed_all? |
Write .tdxn to disk. With output_file set, returns a compact summary (op/annotation counts + file path), NOT the full document -- Read the file for details. Without output_file, returns the full dict like read_tdxn. |
import_network |
target_path, tdn, clear_first?, override? |
Recreate network from a parsed TDXN document (on-disk .tdxn is YAML in v2.0; reads legacy JSON) |
diff_tdxn |
target?, max_changed_ops?, max_bytes? |
What's UNSAVED in TDXN networks (live vs on-disk .tdxn) -- the view git can't give. Omit target -> whole project (every live TDXN COMP, summarized); target = a COMP path OR a .tdxn file path/bare filename -> that one COMP in full detail (old=disk, new=live). For committed/history diffs use plain git diff. Read-only. |
read_tdn and diff_tdn remain registered as DEPRECATED aliases that call straight through -- the old names are published in shipped rule files and saved agent prompts, so they keep working. Prefer read_tdxn / diff_tdxn.
When to prefer read_tdxn: exploring or auditing >=3 operators, checking structure and parameters-as-authored, mapping connections, reading annotations. Scope cost with comp_path; cap with max_depth on large roots.
When NOT to use read_tdxn: evaluated-expression runtime values (get_parameter), cook errors (get_op_errors), DAT/CHOP/TOP output data (get_dat_content, capture_top), cook timing (get_op_performance), flag state after runtime mutation (get_op_flags). read_tdxn is an authored-state snapshot, not a runtime probe.
When to use diff_tdxn: whenever the user asks "what's changed / unsaved?" for TDXN networks. It shows what is UNSAVED -- the live in-memory network vs the on-disk .tdxn -- which git cannot see (git only reads disk, never TD's live state). Omit target (or pass ""/"project") for a whole-project summary (every live TDXN COMP: which changed + counts); pass a target (a COMP path OR a .tdxn file path/bare filename, resolved to its COMP) for one COMP in full detail (old=disk, new=live). For committed/history diffs use plain git diff. Read-only, non-interactive. Requires TD running.
TOP Capture
| Tool |
Parameters |
Description |
capture_op |
op_path, format?, quality?, max_resolution?, inline? |
Capture ANY operator's output: a TOP natively, every other family (CHOP, SOP, POP, DAT, COMP, MAT) through a transient OP Viewer TOP -- what the network editor's viewer shows; the returned text says when a viewer was used. Same temp-file return and Quality verdict as capture_top. |
capture_top |
op_path, format?, quality?, max_resolution?, inline?, sample_grid? |
Capture a TOP output (TOP only; other families -> capture_op). Returns a temp file path by default; inline=True embeds a small preview. sample_grid>=2 returns numeric NxN RGBA cells + channel stats instead of an image, clamped 2..32 with origin at top-left. The returned text carries a Quality verdict from the raw pixels (luminance + alpha stats): a Quality: FAIL flags a black / flat / fully-transparent frame so you can tell an empty render from a real one WITHOUT reading the image. Never declare a visual task done on a FAIL. |
For visual work, success is verified by capturing and judging the output TOP, not by a clean network alone; see /visual-aesthetics.
Logging
| Tool |
Parameters |
Description |
get_logs |
level?, count?, since_id?, source? |
Get recent log entries from ring buffer |
Auto-piggybacked logs: A _logs field rides along only when a WARNING or ERROR was logged during the call (capped at ~8) -- routine INFO/DEBUG/SUCCESS history is omitted to keep responses token-lean. Warning cursors are per session (from the bridge's identity headers), so concurrent sessions each receive their own copy of a warning -- one session polling first no longer consumes it for the others.
Unattended sessions: nothing answers a dialog, so preset the Embody parameter that decides it: Tdxnpalettehandling (palette Black Box vs Full Export), Filecleanup (deleted-file prompt), Tdxnlockedwarn (quiet) and Tdxndatsafety (save-time content report). Saves never prompt: save through save_project and read save warnings in get_job_status(job_id)["warnings"] -- the save's reinit can keep them out of _logs.
Auto-piggybacked peer advisories: a _peers field rides along when your request touches territory another session modified recently (last ~10 min) -- one entry per peer: {label, scope, tool, age_s, conflict}. conflict: true means a peer WROTE an overlapping scope within the last minute AND your operation is also a write -- treat it as a hard stop: check get_sessions, coordinate (or divide work by COMP subtree), and only then proceed. Non-conflict advisories are informational and deduped per (peer, scope) for ~5 min; conflicts always ride.
Destructive-op gate: delete_op, import_network with clear_first=True, run_tests, and batches containing them are REFUSED (MULTI-SESSION GATE error naming the holder/peer) while a live peer session claims the scope or wrote it within the last minute. Pass override=True only when certain, and say so. Call get_logs for the full history, or read the log files in Embody's logs directory (see the Logfolder parameter on the Embody COMP).
Auto-attached recovery hints: when a tool returns an error, a recovery_hints list may ride along -- each entry is {code, cause, action, next_tools} keyed off the error message (path-not-found, wrong family, empty capture, thread conflict, timeout, ...). It tells you the likely cause and which tool to call next, so recover by following it rather than retrying the same failing call verbatim. Every error envelope also carries error_code (envoy.<area>.<condition>, envoy.error when unclassified) -- branch on it, not on the message text.
Write effects: every write tool's response may carry _effects -- errors/warnings that appeared after YOUR write, an fps drop, and for DAT writes the compile state of every GLSL operator consuming that DAT (its dock host and any operator referencing it by parameter): shaders_checked (what was linted), new_shader_errors (failures your write introduced), shader_errors_persist (still failing after a later write). Fix those before building further.
Testing
| Tool |
Parameters |
Description |
run_tests |
suite_name?, test_name?, override?, background?, idempotency_key? |
Run Embody unit-test suites (all, one suite, or one test; 300s timeout). Destructive and agent tiers are excluded -- they run only via their dedicated entry points. Gated by the multi-session destructive-op gate; override bypasses it (say so when you do). background=True (recommended for full runs) returns a job id immediately -- poll get_job_status; the synchronous mode is severed by the watchdog suites' server restart |
get_job_status |
job_id? |
Status of background jobs (run_tests background=True, save_project). Disk-backed (.embody/jobs/), so results survive server restarts and extension reinits; omit job_id to list recent jobs. Finished run_tests jobs carry the summary + failing tests |
save_project |
idempotency_key? |
Save the project as a tracked job: returns a job id immediately, the save runs a few frames later (a synchronous call is severed by the save's own main-thread block + extension reinit). Finished record carries version_before/version_after |
update_embody |
idempotency_key? |
Self-update Embody to the latest GitHub release as a tracked job -- bounded (sha256-pinned manifest, downgrade-refusing), non-interactive, never needs the TD Python grant. Refused in Perform Mode / during test runs. Finished record carries version_before/version_after; the install restarts the server (one reconnect blip) |
Bridge Meta-Tools
These run locally on the STDIO bridge - they work even when TD is not running.
| Tool |
Parameters |
Description |
get_td_status |
(none) |
Check if TD is running, Envoy reachable, crash detection, process liveness. Includes instance registry and live bridge sessions (from heartbeat files -- works even with TD down) |
launch_td |
timeout? |
Launch TD with the project's .toe file, wait for Envoy (default: 120s) |
restart_td |
timeout? |
Gracefully quit TD and relaunch, wait for Envoy (default: 120s) |
list_dialogs |
instance?, screenshot? |
List the modal dialogs a TD instance shows; runs on the bridge so it answers while TD is frozen behind one. TD draws its own dialogs: screenshot=true saves a PNG per dialog to Read. blocked=true when one is up. When every Envoy tool times out, call this FIRST. |
dismiss_dialog |
instance?, dialog?, action? |
Dismiss a blocking dialog and verify it is gone: auto runs close -> escape -> enter; close/escape decline a question, enter accepts its default, so read the screenshot before choosing. Never targets the main window; envoy.dialog.stuck when nothing worked. |
switch_instance |
instance?, all_sessions? |
List all registered TD instances (omit instance) or re-pin THIS session's bridge to a different running instance (provide toe basename without .toe). For ONE call, pass instance=<name> on any Envoy tool instead -- routed there without changing the pin; an unknown/unreachable name fails only that call (envoy.instance.*). Peers are untouched unless all_sessions=true (writes the registry default and bumps active_epoch, moving every session). See /multi-instance skill for workflow |
Convoy Tools (LAN work relay)
Seventeen additional meta-tools drive Convoy, relaying work to Convoy-enabled Embody nodes on the trusted LAN through the local per-user host app. Status and inventory calls (get_convoy_status, convoy_list_nodes, convoy_list_controllers, convoy_ping) never wake TouchDesigner.
convoy_select_node pins THIS session to one exact node -- ordinary Envoy tools then run there until convoy_select_node with clear=true.
convoy_call / convoy_batch run registered operations on explicit one-off targets; pass a unique idempotency_key per intended action so a retry reconciles instead of double-running.
convoy_get_job / convoy_ack_job / convoy_cancel_job reconcile durable deliveries; acknowledge a finished delivery you have safely observed so the target can release its protected result artifacts.
convoy_forget_node deletes a stale node row on THIS machine's host app (a renamed, moved, or deleted project's leftover). It refuses only while the node has a delivery that has not FINISHED, and names the blocking delivery ids in its refusal (a finished-but-unacknowledged result never holds a row -- results are fetched by delivery id and outlive it); dead and long-unseen rows are also evicted automatically by the host's retention sweep.
convoy_get_artifact / convoy_save_artifact fetch results BY ARTIFACT REFERENCE and verify them locally -- never open a remote C:\... or /Users/... path as if it were local.
convoy_start_node / convoy_restart_node manage node lifecycle; restarts require the node's CURRENT runtime id (from convoy_list_nodes) plus a unique idempotency_key, and the default policy refuses dirty or unverifiable project state.
convoy_update_embody self-updates Embody on one node (node=<id|name|hostname>) or the whole fleet (all=true) by dispatching the bounded update_embody operation as durable per-node jobs -- no TD Python grant involved; offline/disabled/Perform Mode nodes are skipped by name.
convoy_owlette is an optional read-mostly site bridge that fails closed without credentials.
convoy_lifecycle_state / convoy_lifecycle_quit are TD-side tools reserved for the Convoy host's own lifecycle session; any other session gets Convoy lifecycle host session required. Use convoy_restart_node instead.
Batch Operations
| Tool |
Parameters |
Description |
batch_operations |
operations, override? |
Execute multiple operations in a single request. Reduces latency and token overhead; override applies to gated entries |
operations is a list of {"tool": str, "params": dict} objects. Each entry maps to an existing tool name and its parameters. Stops on first error.
When to use: 3+ calls to the same tool type (positioning, connecting, parameter setting, flags). Use execute_python instead when you need conditionals, loops, or computed values between operations.
Example - position 4 operators + connect them in one call:
{"operations": [
{"tool": "set_op_position", "params": {"op_path": "/project1/noise1", "x": 400, "y": 0}},
{"tool": "set_op_position", "params": {"op_path": "/project1/comp1", "x": 800, "y": 0}},
{"tool": "set_op_position", "params": {"op_path": "/project1/level1", "x": 1200, "y": 0}},
{"tool": "set_op_position", "params": {"op_path": "/project1/null1", "x": 1600, "y": 0}},
{"tool": "connect_ops", "params": {"source_path": "/project1/noise1", "dest_path": "/project1/comp1"}},
{"tool": "connect_ops", "params": {"source_path": "/project1/comp1", "dest_path": "/project1/level1"}},
{"tool": "connect_ops", "params": {"source_path": "/project1/level1", "dest_path": "/project1/null1"}}
]}
1---2name: mcp-tools-reference3description: MUST READ before first MCP tool call in a session. Complete Envoy tool catalog with parameters and usage.4---5<!-- Generated by Embody/Envoy - Do not remove this comment -->67# Envoy MCP Tool Reference89Mutating TD-authoring operations are wrapped in TD undo blocks (one batch_operations call = one Ctrl+Z step); read-only tools, run_tests, cook_op, and disk-only ops (export_network, save_externalization) are not.1011## Operator Management1213| Tool | Parameters | Description |14|------|-----------|-------------|15| `create_op` | `parent_path`, `op_type`, `name?` | Create a new operator (e.g., `baseCOMP`, `noiseTOP`, `textDAT`, `gridPOP`). Auto-positions it and hugs any docked companions below it (`docks_placed`) |16| `create_extension` | `parent_path`, `class_name`, `name?`, `code?`, `promote?`, `ext_name?`, `ext_index?`, `existing_comp?`, `parent_shortcut?` | Create a TD extension: baseCOMP + text DAT + extension wiring |17| `delete_op` | `op_path`, `override?` | Delete an operator (multi-session gated; `override` bypasses, say so) |18| `copy_op` | `source_path`, `dest_parent`, `new_name?` | Copy operator to new location. Auto-positions the copy and hugs its docked companions (`docks_placed`) |19| `rename_op` | `op_path`, `new_name` | Rename an operator |20| `get_op` | `op_path`, `include_defaults?` | Returns NON-DEFAULT parameters by default (`include_defaults=True` for all); parameter-heavy COMPs are expensive (~3k+ tokens full) -- prefer `read_tdxn` for structure reads |21| `query_network` | `parent_path?`, `recursive?`, `op_type?`, `include_utility?` | Compact operator list: path/type/family/depth; name = last path segment. Set `include_utility=True` to include annotations |22| `find_children` | `op_path`, `name?`, `type?`, `depth?`, `tags?`, `text?`, `comment?`, `include_utility?` | Advanced search using TD's `findChildren` |23| `cook_op` | `op_path`, `force?`, `recurse?` | Force-cook an operator |2425## Parameter Control2627| Tool | Parameters | Description |28|------|-----------|-------------|29| `set_parameter` | `op_path`, `par_name`, `value?`, `mode?`, `expr?`, `bind_expr?` | Set value, expression, bind expression, or mode; rejects invalid Menu values with valid `menuNames`; sequence-block names auto-grow (`const5name` -> 6 blocks) |30| `get_parameter` | `op_path`, `par_name?`, `search?`, `search_in?`, `depth?`, `max_results?`, `details?` | Single-parameter mode is compact by default (`path`, `parameter`, `value`, `mode`, `label`, mode-specific refs, `menuNames`); `details=True` restores default/style/range/menuLabels/menuIndex. Search ignores `details` |3132## DAT Content3334| Tool | Parameters | Description |35|------|-----------|-------------|36| `get_dat_content` | `op_path`, `format?` | Get DAT text or table data (`"text"`, `"table"`, `"auto"`), or `"stats"` to reduce a table to per-column stats + head/tail rows |37| `set_dat_content` | `op_path`, `text?`, `rows?`, `clear?`, `confirm_wipe?` | Full-replace DAT content. Refuses no-action calls and wipes (`text=""`, `rows=[]`, or `clear=True` with no content) unless `confirm_wipe=True`. For partial text edits prefer `edit_dat_content`; use this for tables, full rewrites, or intentional wipes. |38| `edit_dat_content` | `op_path`, `old_string`, `new_string`, `replace_all?`, `confirm_wipe?` | Surgical text edit on a text DAT. `old_string` must appear exactly once by default; widen with context or pass `replace_all=True`. Refuses empty/identical strings and wipes unless `confirm_wipe=True`; not-found errors include diagnostics. Tables go through `set_dat_content(rows=...)`. |3940## CHOP / POP Data4142Reduced reads -- never a blind dump. A 4x600 CHOP is 2,400 raw floats; these43return the shape and range instead.4445| Tool | Parameters | Description |46|------|-----------|-------------|47| `get_chop_data` | `op_path`, `channels?`, `samples?`, `compare_to?` | Per-channel `min`/`max`/`mean`/`std`/`first`/`last`, capped at 32 channels. `channels` is a name glob; `samples>0` adds head/tail raw values. `compare_to` another CHOP adds a `diff` of per-channel deltas -- the "what did this chain do to my data" read |48| `get_pop_data` | `op_path`, `attributes?`, `samples?`, `max_points?` | Point/prim/vert attribute metadata (name, size, type). Metadata costs ~0.02ms at ANY point count; reading point VALUES is a GPU->CPU readback (~9.5ms at 16k points, ~69ms at 160k -- about 4 frames at 60fps), so `samples>0` is opt-in and refused above `max_points` (default 50,000) |4950**Why the POP guard is on point count, not sample count**: `POP.points(attr, startIndex, count)` accepts both slicing arguments and ignores them (verified on 2025.33070) -- the readback happens regardless. Asking for 4,096 points from a 160k POP measured *slower* than reading all of them. Reach for `samples` only when you actually need values, and prefer a smaller upstream POP for inspection.5152## Operator Flags5354| Tool | Parameters | Description |55|------|-----------|-------------|56| `get_op_flags` | `op_path` | Get all flags |57| `set_op_flags` | `op_path`, `bypass?`, `lock?`, `display?`, `render?`, `viewer?`, `current?`, `expose?`, `allowCooking?`, `selected?`, `cloneImmune?`, `componentCloneImmune?`, `showCustomOnly?`, `showDocked?` | Set one or more flags. The last four are the authored flags TDXN round-trips; a flag the operator cannot carry (e.g. `componentCloneImmune` on a TOP) comes back in `unsupported_flags` rather than being silently dropped |5859## Operator Positioning & Layout6061| Tool | Parameters | Description |62|------|-----------|-------------|63| `get_op_position` | `op_path` | Get position, size, color, and comment |64| `get_network_layout` | `comp_path`, `include_annotations?` | Compact layout for all children in a COMP: path/type/nodeX/nodeY/nodeWidth/nodeHeight plus bounding_box; docked ops carry `dockedTo` (host name). Centers are `nodeX+nodeWidth/2`; annotation text is capped at 160 chars. Use instead of repeated `get_op_position` calls |65| `set_op_position` | `op_path`, `x?`, `y?`, `width?`, `height?`, `color?`, `comment?` | Set position, size, color (`[r,g,b]` 0-1), or comment. Moving a host re-hugs its docked companions below the new spot (`docks_moved`); position the host before any explicitly-placed dock |66| `layout_children` | `op_path` | Auto-layout all children in a COMP |6768## Annotations6970| Tool | Parameters | Description |71|------|-----------|-------------|72| `create_annotation` | `parent_path`, `mode?`, `text?`, `title?`, `x?`, `y?`, `width?`, `height?`, `color?`, `opacity?`, `name?` | Create annotation (`"annotate"`, `"comment"`, `"networkbox"`) |73| `get_annotations` | `parent_path` | List all annotations with properties and enclosed operators |74| `set_annotation` | `op_path`, `text?`, `title?`, `color?`, `opacity?`, `width?`, `height?`, `x?`, `y?` | Modify annotation properties |75| `get_enclosed_ops` | `op_path` | Get ops enclosed by annotation, or annotations enclosing an op |7677## Performance Monitoring7879| Tool | Parameters | Description |80|------|-----------|-------------|81| `get_op_performance` | `op_path`, `include_children?` | Get CPU/GPU cook times, memory, cook counts |82| `get_project_performance` | `include_hotspots?` | Get project-level FPS, frame time, GPU/CPU memory, dropped frames, active ops, GPU temp. Optional hotspot ranking of top N COMPs by cook time |8384## Connections8586| Tool | Parameters | Description |87|------|-----------|-------------|88| `connect_ops` | `source_path`, `dest_path`, `source_index?`, `dest_index?`, `comp?` | Wire two operators. `comp=True` for COMP connectors |89| `disconnect_op` | `op_path`, `input_index?`, `comp?` | Disconnect an input |90| `get_connections` | `op_path` | Get all input/output connections |9192## Code Execution9394| Tool | Parameters | Description |95|------|-----------|-------------|96| `execute_python` | `code` | Execute Python in TD. Set `result` variable to return values |9798**Envoy's own host**: `execute_python` refuses (nothing runs; `error_code` `envoy.embody.host_destroy_refused`) code that would destroy or reload the Embody COMP, an ancestor, `/` or Envoy's extension DAT in the same call -- `me` and `parent()` ARE the Embody COMP here. `delete_op`, destroy-class `exec_op_method` and reload pulses via `set_parameter` refuse the same targets. Stop and ask the user; only when they asked to move or remove Embody, follow the refusal text (one deferred `run()` string).99100## Introspection & Diagnostics101102| Tool | Parameters | Description |103|------|-----------|-------------|104| `get_td_info` | _(none)_ | TD version, build, OS, Envoy version |105| `get_op_errors` | `op_path`, `recurse?` | Errors and warnings for op and children. Covers cook errors, Python tracebacks (`kind: "script"` entries -- invisible to `op.errors()`), and GLSL compile failures (`shaderErrors`) |106| `exec_op_method` | `op_path`, `method`, `args?`, `kwargs?` | Call a method on an operator |107| `get_td_classes` | _(none)_ | List all Python classes in `td` module |108| `get_td_class_details` | `class_name` | Get methods, properties, docs for a TD class |109| `get_module_help` | `module_name` | Python help text for a module |110| `get_docs` | `query`, `section?`, `source?`, `max_chars?` | Look up official TD docs; offline mirror preferred, docs.derivative.ca fallback; normal responses return `title`, `source`, `sections_available`, `content`; ambiguous offline lookups return `matches` instead of a page |111| `get_guidance` | `topic?` | Read THIS project's checked-in TouchDesigner doctrine (`.claude/rules/*.md`, `.claude/skills/*/SKILL.md`). Bare call lists topics; `topic` returns one document. Matching ignores case/punctuation. Answered worker-side (no TD round-trip). Complements `get_docs`: `get_docs` is official Derivative documentation, `get_guidance` is how this project wants the work done -- and it is the only way clients with no skills folder (VS Code, Copilot, Windsurf) see them; Codex, Cursor, Gemini and Antigravity read `.agents/skills/`, OpenCode reads `.claude/skills/` |112| `get_focus` | _(none)_ | What the user is looking at: `network` (current pane), `selected`, `current`, `rollover`, plus `target`/`targetSource` and a `note`. Call it to resolve "this operator" / "fix this" before guessing a path. DISAMBIGUATION: "this" means the SELECTED/current op, NEVER the rollover (rollover is incidental mouse position). Headless TD returns `headless: true` with nulls |113| `get_sessions` | _(none)_ | List AI client sessions connected to this Envoy (sid, label, pid, idle, last tool, `recent_scopes` = op paths/files recently modified, `claims` = scopes held, stale flag) plus `you` = caller's own sid. Check at session start and before large or destructive operations so concurrent sessions don't clobber each other. Response may include `worktrees`: in-flight durable worktree tasks, visible even after their session ended |114| `claim_scope` | `scope`, `note?`, `ttl?` | Cooperative WRITE lease on an op-path prefix, `file:<repo-relative>` path, or `project:<name>` scope. Peers' overlapping claims are refused while yours is live; their destructive ops on it are gated. Auto-renews on your own writes; expires on TTL or session silence. `project:worktree-*` claims are DURABLE: they survive session death and Envoy restarts, expiring when the worktree directory is removed (7-day backstop) |115| `release_scope` | `scope` | Release a lease you hold (polite; expiry also handles it) |116| `announce_task` | `title`, `scopes?`, `note?` | Announce a unit of work to the shared task ledger (`.embody/tasks.json`) so parallel sessions see what is being worked on and what is FINISHED but uncommitted. Announce at the start of substantive work (a feature, fix, refactor); keep it honest with `update_task`. Active entries ride on `get_sessions` for every session |117| `update_task` | `task_id`, `status?`, `note?`, `commit?` | Transition a ledger task: `done_uncommitted` when the work is finished but sitting uncommitted in the tree (the state peers MUST see), `committed` with the sha once it lands (a sha alone implies the transition), `abandoned` when dropped. Any session may update any task -- non-owner writes record `updated_by` |118| `preflight_landing` | `worktree_path` | Landing safety check for a worktree diff -- intersects the files it would land with main-tree dirt, peer `file:` claims/touches, and unsaved live TDXN state (runtime dirty state; legacy tsv `dirty` columns still honored). Run BEFORE porting any worktree diff; verdict `conflicts` means reconcile first |119120## MCP Prompts121122| Prompt | Parameters | Description |123|--------|-----------|-------------|124| `search_op` | `op_name`, `op_type?` | Guide for searching operators |125| `check_op_errors` | `op_path` | Guide for inspecting/resolving errors and warnings |126| `connect_ops` | _(none)_ | Guide for wiring operators |127| `create_extension_guide` | _(none)_ | Guide for creating extensions |128129## Embody Integration130131| Tool | Parameters | Description |132|------|-----------|-------------|133| `externalize_op` | `op_path`, `tag_type?` | Tag and externalize operator to disk (one step) |134| `remove_externalization_tag` | `op_path`, `delete_file?` | Remove externalization tracking (tag + row + TDXN breadcrumb); `delete_file=True` also deletes the file (best-effort). Returns `removed_tags`, `removed_rows`, `removed_anything`, `summary` -- an operator can have a tracked row but NO tag, so check `removed_anything`, not `removed_tags`, to confirm cleanup |135| `get_externalizations` | _(none)_ | List all externalized operators |136| `save_externalization` | `op_path` | Force re-export an already-externalized operator |137| `get_externalization_status` | `op_path` | Get dirty state, build, timestamp, path |138139## TDXN Network Format140141| Tool | Parameters | Description |142|------|-----------|-------------|143| `read_tdxn` | `comp_path?`, `include_dat_content?`, `max_depth?`, `embed_all?` | **Preferred for reading >=3 operators.** Returns live network as a TDXN dict. ~20-90x fewer tokens than `get_op`+`query_network` walks thanks to default-omission, type_defaults, and par_templates. |144| `export_network` | `root_path?`, `include_dat_content?`, `output_file?`, `max_depth?`, `embed_all?` | Write `.tdxn` to disk. **With `output_file` set, returns a compact summary (op/annotation counts + file path), NOT the full document** -- Read the file for details. Without `output_file`, returns the full dict like `read_tdxn`. |145| `import_network` | `target_path`, `tdn`, `clear_first?`, `override?` | Recreate network from a parsed TDXN document (on-disk `.tdxn` is YAML in v2.0; reads legacy JSON) |146| `diff_tdxn` | `target?`, `max_changed_ops?`, `max_bytes?` | **What's UNSAVED in TDXN networks** (live vs on-disk `.tdxn`) -- the view git can't give. Omit `target` -> whole project (every live TDXN COMP, summarized); `target` = a COMP path OR a `.tdxn` file path/bare filename -> that one COMP in full detail (`old`=disk, `new`=live). For committed/history diffs use plain `git diff`. Read-only. |147148`read_tdn` and `diff_tdn` remain registered as DEPRECATED aliases that call straight through -- the old names are published in shipped rule files and saved agent prompts, so they keep working. Prefer `read_tdxn` / `diff_tdxn`.149150**When to prefer `read_tdxn`:** exploring or auditing >=3 operators, checking structure and parameters-as-authored, mapping connections, reading annotations. Scope cost with `comp_path`; cap with `max_depth` on large roots.151152**When NOT to use `read_tdxn`:** evaluated-expression runtime values (`get_parameter`), cook errors (`get_op_errors`), DAT/CHOP/TOP output data (`get_dat_content`, `capture_top`), cook timing (`get_op_performance`), flag state after runtime mutation (`get_op_flags`). `read_tdxn` is an authored-state snapshot, not a runtime probe.153154**When to use `diff_tdxn`:** whenever the user asks "what's changed / unsaved?" for TDXN networks. It shows what is UNSAVED -- the live in-memory network vs the on-disk `.tdxn` -- which **git cannot see** (git only reads disk, never TD's live state). Omit `target` (or pass `""`/`"project"`) for a **whole-project** summary (every live TDXN COMP: which changed + counts); pass a `target` (a COMP path OR a `.tdxn` file path/bare filename, resolved to its COMP) for **one COMP in full detail** (`old`=disk, `new`=live). For **committed/history** diffs use plain `git diff`. Read-only, non-interactive. Requires TD running.155156## TOP Capture157158| Tool | Parameters | Description |159|------|-----------|-------------|160| `capture_op` | `op_path`, `format?`, `quality?`, `max_resolution?`, `inline?` | Capture ANY operator's output: a TOP natively, every other family (CHOP, SOP, POP, DAT, COMP, MAT) through a transient OP Viewer TOP -- what the network editor's viewer shows; the returned text says when a viewer was used. Same temp-file return and Quality verdict as `capture_top`. |161| `capture_top` | `op_path`, `format?`, `quality?`, `max_resolution?`, `inline?`, `sample_grid?` | Capture a TOP output (TOP only; other families -> `capture_op`). Returns a temp file path by default; `inline=True` embeds a small preview. `sample_grid>=2` returns numeric NxN RGBA cells + channel stats instead of an image, clamped 2..32 with origin at top-left. The returned text carries a **Quality verdict** from the raw pixels (luminance + alpha stats): a `Quality: FAIL` flags a black / flat / fully-transparent frame so you can tell an empty render from a real one WITHOUT reading the image. Never declare a visual task done on a FAIL. |162163For visual work, success is verified by capturing and judging the output TOP, not by a clean network alone; see `/visual-aesthetics`.164165## Logging166167| Tool | Parameters | Description |168|------|-----------|-------------|169| `get_logs` | `level?`, `count?`, `since_id?`, `source?` | Get recent log entries from ring buffer |170171**Auto-piggybacked logs**: A `_logs` field rides along **only when a WARNING or ERROR was logged during the call** (capped at ~8) -- routine INFO/DEBUG/SUCCESS history is omitted to keep responses token-lean. Warning cursors are **per session** (from the bridge's identity headers), so concurrent sessions each receive their own copy of a warning -- one session polling first no longer consumes it for the others.172173**Unattended sessions**: nothing answers a dialog, so preset the Embody parameter that decides it: `Tdxnpalettehandling` (palette Black Box vs Full Export), `Filecleanup` (deleted-file prompt), `Tdxnlockedwarn` (`quiet`) and `Tdxndatsafety` (save-time content report). Saves never prompt: save through `save_project` and read save warnings in `get_job_status(job_id)["warnings"]` -- the save's reinit can keep them out of `_logs`.174175**Auto-piggybacked peer advisories**: a `_peers` field rides along when your request touches territory another session modified recently (last ~10 min) -- one entry per peer: `{label, scope, tool, age_s, conflict}`. `conflict: true` means a peer WROTE an overlapping scope within the last minute AND your operation is also a write -- **treat it as a hard stop**: check `get_sessions`, coordinate (or divide work by COMP subtree), and only then proceed. Non-conflict advisories are informational and deduped per (peer, scope) for ~5 min; conflicts always ride.176177**Destructive-op gate**: `delete_op`, `import_network` with `clear_first=True`, `run_tests`, and batches containing them are REFUSED (`MULTI-SESSION GATE` error naming the holder/peer) while a live peer session claims the scope or wrote it within the last minute. Pass `override=True` only when certain, and say so. Call `get_logs` for the full history, or read the log files in Embody's logs directory (see the `Logfolder` parameter on the Embody COMP).178179**Auto-attached recovery hints**: when a tool returns an `error`, a `recovery_hints` list may ride along -- each entry is `{code, cause, action, next_tools}` keyed off the error message (path-not-found, wrong family, empty capture, thread conflict, timeout, ...). It tells you the likely cause and which tool to call next, so recover by following it rather than retrying the same failing call verbatim. Every error envelope also carries `error_code` (`envoy.<area>.<condition>`, `envoy.error` when unclassified) -- branch on it, not on the message text.180181**Write effects**: every write tool's response may carry `_effects` -- errors/warnings that appeared after YOUR write, an fps drop, and for DAT writes the compile state of every GLSL operator consuming that DAT (its dock host and any operator referencing it by parameter): `shaders_checked` (what was linted), `new_shader_errors` (failures your write introduced), `shader_errors_persist` (still failing after a later write). Fix those before building further.182183## Testing184185| Tool | Parameters | Description |186|------|-----------|-------------|187| `run_tests` | `suite_name?`, `test_name?`, `override?`, `background?`, `idempotency_key?` | Run Embody unit-test suites (all, one suite, or one test; 300s timeout). Destructive and agent tiers are excluded -- they run only via their dedicated entry points. Gated by the multi-session destructive-op gate; `override` bypasses it (say so when you do). `background=True` (recommended for full runs) returns a job id immediately -- poll `get_job_status`; the synchronous mode is severed by the watchdog suites' server restart |188| `get_job_status` | `job_id?` | Status of background jobs (`run_tests background=True`, `save_project`). Disk-backed (`.embody/jobs/`), so results survive server restarts and extension reinits; omit `job_id` to list recent jobs. Finished run_tests jobs carry the summary + failing tests |189| `save_project` | `idempotency_key?` | Save the project as a tracked job: returns a job id immediately, the save runs a few frames later (a synchronous call is severed by the save's own main-thread block + extension reinit). Finished record carries version_before/version_after |190| `update_embody` | `idempotency_key?` | Self-update Embody to the latest GitHub release as a tracked job -- bounded (sha256-pinned manifest, downgrade-refusing), non-interactive, never needs the TD Python grant. Refused in Perform Mode / during test runs. Finished record carries version_before/version_after; the install restarts the server (one reconnect blip) |191192## Bridge Meta-Tools193194These run locally on the STDIO bridge - they work even when TD is not running.195196| Tool | Parameters | Description |197|------|-----------|-------------|198| `get_td_status` | _(none)_ | Check if TD is running, Envoy reachable, crash detection, process liveness. Includes instance registry and live bridge `sessions` (from heartbeat files -- works even with TD down) |199| `launch_td` | `timeout?` | Launch TD with the project's `.toe` file, wait for Envoy (default: 120s) |200| `restart_td` | `timeout?` | Gracefully quit TD and relaunch, wait for Envoy (default: 120s) |201| `list_dialogs` | `instance?`, `screenshot?` | List the modal dialogs a TD instance shows; runs on the bridge so it answers while TD is frozen behind one. TD draws its own dialogs: `screenshot=true` saves a PNG per dialog to Read. `blocked=true` when one is up. When every Envoy tool times out, call this FIRST. |202| `dismiss_dialog` | `instance?`, `dialog?`, `action?` | Dismiss a blocking dialog and verify it is gone: `auto` runs close -> escape -> enter; close/escape decline a question, enter accepts its default, so read the screenshot before choosing. Never targets the main window; `envoy.dialog.stuck` when nothing worked. |203| `switch_instance` | `instance?`, `all_sessions?` | List all registered TD instances (omit `instance`) or re-pin THIS session's bridge to a different running instance (provide toe basename without `.toe`). For ONE call, pass `instance=<name>` on any Envoy tool instead -- routed there without changing the pin; an unknown/unreachable name fails only that call (`envoy.instance.*`). Peers are untouched unless `all_sessions=true` (writes the registry default and bumps `active_epoch`, moving every session). See `/multi-instance` skill for workflow |204205### Convoy Tools (LAN work relay)206207Seventeen additional meta-tools drive Convoy, relaying work to Convoy-enabled Embody nodes on the trusted LAN through the local per-user host app. Status and inventory calls (`get_convoy_status`, `convoy_list_nodes`, `convoy_list_controllers`, `convoy_ping`) never wake TouchDesigner.208209- `convoy_select_node` pins THIS session to one exact node -- ordinary Envoy tools then run there until `convoy_select_node` with `clear=true`.210- `convoy_call` / `convoy_batch` run registered operations on explicit one-off targets; pass a unique `idempotency_key` per intended action so a retry reconciles instead of double-running.211- `convoy_get_job` / `convoy_ack_job` / `convoy_cancel_job` reconcile durable deliveries; acknowledge a finished delivery you have safely observed so the target can release its protected result artifacts.212- `convoy_forget_node` deletes a stale node row on THIS machine's host app (a renamed, moved, or deleted project's leftover). It refuses only while the node has a delivery that has not FINISHED, and names the blocking delivery ids in its refusal (a finished-but-unacknowledged result never holds a row -- results are fetched by delivery id and outlive it); dead and long-unseen rows are also evicted automatically by the host's retention sweep.213- `convoy_get_artifact` / `convoy_save_artifact` fetch results BY ARTIFACT REFERENCE and verify them locally -- never open a remote `C:\...` or `/Users/...` path as if it were local.214- `convoy_start_node` / `convoy_restart_node` manage node lifecycle; restarts require the node's CURRENT runtime id (from `convoy_list_nodes`) plus a unique `idempotency_key`, and the default policy refuses dirty or unverifiable project state.215- `convoy_update_embody` self-updates Embody on one node (`node=<id|name|hostname>`) or the whole fleet (`all=true`) by dispatching the bounded `update_embody` operation as durable per-node jobs -- no TD Python grant involved; offline/disabled/Perform Mode nodes are skipped by name.216- `convoy_owlette` is an optional read-mostly site bridge that fails closed without credentials.217- `convoy_lifecycle_state` / `convoy_lifecycle_quit` are TD-side tools reserved for the Convoy host's own lifecycle session; any other session gets `Convoy lifecycle host session required`. Use `convoy_restart_node` instead.218219## Batch Operations220221| Tool | Parameters | Description |222|------|-----------|-------------|223| `batch_operations` | `operations`, `override?` | Execute multiple operations in a single request. Reduces latency and token overhead; `override` applies to gated entries |224225**`operations`** is a list of `{"tool": str, "params": dict}` objects. Each entry maps to an existing tool name and its parameters. Stops on first error.226227**When to use**: 3+ calls to the same tool type (positioning, connecting, parameter setting, flags). Use `execute_python` instead when you need conditionals, loops, or computed values between operations.228229**Example** - position 4 operators + connect them in one call:230```json231{"operations": [232 {"tool": "set_op_position", "params": {"op_path": "/project1/noise1", "x": 400, "y": 0}},233 {"tool": "set_op_position", "params": {"op_path": "/project1/comp1", "x": 800, "y": 0}},234 {"tool": "set_op_position", "params": {"op_path": "/project1/level1", "x": 1200, "y": 0}},235 {"tool": "set_op_position", "params": {"op_path": "/project1/null1", "x": 1600, "y": 0}},236 {"tool": "connect_ops", "params": {"source_path": "/project1/noise1", "dest_path": "/project1/comp1"}},237 {"tool": "connect_ops", "params": {"source_path": "/project1/comp1", "dest_path": "/project1/level1"}},238 {"tool": "connect_ops", "params": {"source_path": "/project1/level1", "dest_path": "/project1/null1"}}239]}