Skip to content
Automation
Skill

/workflow-layout

Lay out and organize a ComfyUI workflow cleanly on the live panel canvas. Dependency-layered node placement with no overlaps, subgraphs, colored group boxes, and subgraph rail alignment. Use when asked to tidy / clean up / organize / arrange a workflow, add groups or subgraphs,

From plugin
comfyui-mcp
74842 skills4 agents11 commands1 MCP
Install
$ npx -y skills add artokun/comfyui-mcp --skill workflow-layout --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/workflow-layout

Context preview

The summary Claude sees to decide when to auto-load this skill.

Lay out and organize a ComfyUI workflow cleanly on the live panel canvas. Dependency-layered node placement with no overlaps, subgraphs, colored group boxes, and subgraph rail alignment. Use when asked to tidy / clean up / organize / arrange a workflow, add groups or subgraphs,

SKILL.md

workflow-layout.SKILL.md
name: workflow-layout
description: Lay out and organize a ComfyUI workflow cleanly on the live panel canvas. Dependency-layered node placement with no overlaps, subgraphs, colored group boxes, and subgraph rail alignment. Use when asked to tidy / clean up / organize / arrange a workflow, add groups or subgraphs, fix overlapping nodes, or build a workflow that should look good from the start.

ComfyUI Workflow Layout & Organization

Turn a tangled graph into a clean left-to-right dataflow a human reads at a glance, using the `panel_*` canvas tools. The golden rule: never lay out blind. Read the real node sizes and rail positions first, then compute positions from them.

Primitives (panel tools)

  • `panel_query_graph {fields:'detail', limit:200, max_chars:60000}`. READ FIRST, every time. Returns, for the graph you're viewing:
  • each node's `pos` [x,y], `size` [w,h] (body only), and `full_height` (the

TRUE rendered footprint = title bar + body; use this for vertical stacking),

  • all `groups` (`id`, `title`, `color`, `bounding [x,y,w,h]`),
  • and, only when inside a subgraph, the `rails`: the `input` / `output` boundary

node positions. Everything below is computed from these numbers.

  • `panel_edit_node({node_id, pos?, size?, title?, collapsed?, preset?|color?|bgcolor?})`

atomically moves, resizes, retitles, collapses, or color-codes a node. Widget values remain on their dedicated tool; execution mode is available as `mode` when needed.

  • Groups (colored boxes; nodes stay put): `panel_create_group` (pass `node_ids` to

auto-wrap, or `bounds [x,y,w,h]`; `color` hex), `panel_move_group`, `panel_edit_group`, `panel_remove_group`.

  • Subgraphs (nest nodes into one collapsible node): `panel_create_subgraph(node_ids)`,

`panel_enter_subgraph` / `panel_exit_subgraph`, `panel_get_subgraph`, `panel_promote_widget`, `panel_move_rail(rail, [x,y])` (`rail` = `"input"|"output"`, must be inside the subgraph), `panel_expose_subgraph_output(from_node_id, from_output)` / `panel_expose_subgraph_input(to_node_id, to_input)` (expose an interior slot on the boundary rail; must be inside the subgraph), and `panel_unpack_subgraph(node_id)` (expand/dissolve a subgraph back into the parent, the inverse of `panel_create_subgraph`).

  • `panel_canvas({action:"fit"})` to frame the result; `panel_save_workflow` to persist.

The layout algorithm (dependency-layered, overlap-free)

1. Read the graph. Build a DAG from each input's `connected_from` (ignore unconnected widget inputs; only node→node edges matter). 2. Layer every node by longest path from a source: `layer(n) = 0` if it has no incoming node edges, else `1 + max(layer(of its sources))`. Layers become columns, left → right. 3. X by layer: `x = X0 + layer * COL_PITCH`, where `COL_PITCH ≈ widest node.size[0] in that column + ~80`. 4. Y by FULL height (this is what stops overlaps): stack a column top→down with `y[i+1] = y[i] + node[i].full_height + ROW_GAP`. Use `full_height` (from `panel_query_graph` detail rows), NOT `size[1]`. `size[1]` is the BODY only (slots + widgets); the title bar renders ~30px ABOVE `pos` and is NOT in `size[1]`, so stacking by `size[1]` overlaps every node by a header (the classic "headers eating the node above" bug). `full_height` already includes that header (and is only the title height for a collapsed node), so `y += full_height + ROW_GAP` lands an exact `ROW_GAP` gap between the previous node's bottom and the next node's title. Never use a fixed row pitch. Tall nodes (KSampler, WanVideo Sampler, LoRA-select) are 480 to 600px and WILL overlap a 320 pitch. (If `full_height` is ever absent, fall back to `size[1] + ~30` for the header.) 5. Order within a column to cut wire crossings: place each node near the average Y of its connected nodes (a median/barycenter pass is plenty).

Reads-well constants: `COL_PITCH` 360 to 480, `ROW_GAP` 40. Because `full_height` already accounts for the title bar, you don't add extra top headroom per node. `ROW_GAP` is the clean gap you'll actually see.

Subgraph interiors — move the rails!

A subgraph has two boundary rails (input left, output right). They do not follow the inner nodes. Move the nodes without moving the rails and you get a huge gap (a common mistake). For each subgraph: `panel_enter_subgraph` → lay out the inner nodes (algorithm above) → then pin the rails to the node band:

  • input rail → `panel_move_rail("input", [minNodeX - 180, bandTopY])`
  • output rail → `panel_move_rail("output", [maxNodeX + 60, bandTopY])`

Keep rails at the same Y as the first row. Read current rail positions from `panel_query_graph` (`rails`) before deciding. `panel_exit_subgraph` when done.

**Wiring interior nodes to the boundary (don't connect to a guessed rail id).** To expose an interior node's output/input on the boundary so the PARENT graph can wire it, do NOT `panel_connect` to a rail node id you guessed. Use `panel_expose_subgraph_output(from_node_id, from_output)` (interior output → output rail) and `panel_expose_subgraph_input(to_node_id, to_input)` (interior input → input rail), both while inside the subgraph. `panel_query_graph`'s `rails` shows which boundary slots already exist and which still need exposing. To expand or dissolve a subgraph back into the parent (inline its inner nodes and rewire external links, removing the wrapper, the inverse of `panel_create_subgraph`), use `panel_unpack_subgraph(node_id)`. All undoable with Ctrl+Z.

Groups vs subgraphs — choose deliberately

  • Group (colored box): lightweight visual band; nodes stay in place and editable. Reach

for this first, to label regions of a flat graph or band stage-columns at the root.

  • Subgraph: collapses a stage into one node. Useful for large graphs, but it nests and hides

nodes and adds boundary ports. Don't subgraph everything. A 2 or 3 node stage rarely earns it, and over-subgraphing hurts readability and complicates packaging and hand

Read more
Ships withcomfyui-mcp

This project is no longer maintained. ComfyUI now ships official agent and MCP tooling — Comfy Agent and Comfy MCP — built and supported by the Comfy-Org team with deeper integration than a community project can match.

Get the whole plugin

Other skills on comfyui-mcp.