---
name: rgthree
description: Configure and author rgthree-comfy nodes — Fast Groups Bypasser/Muter (group toggles), Power Lora Loader, Context/Context Big, Seed, Any Switch. Use when a workflow contains rgthree nodes, when asked to add stage/section toggles or an A/B switch, when stacking LoRAs, or when an rgthree node needs configuring. Covers the frontend-only nodes that are absent from /object_info and the properties-not-widgets configuration model.
---

# rgthree-comfy

`rgthree-comfy` is one of the most widely installed packs, so its nodes turn up in a
large share of community workflows. Three of its properties make it a recurring
agent-failure mode — all three fail *quietly enough* to look like success.

## The three things that catch agents out

**1. Some rgthree nodes are FRONTEND-ONLY.** They are registered by the pack's JS
(`registerCustomNodes()`), have no Python class, and are therefore **absent from
`/object_info` by design**. Checking `/object_info` and concluding "this node doesn't
exist" is wrong. `/object_info` lists 24 rgthree *backend* types; the toggles are not
among them.

**2. They are configured through PROPERTIES, not widgets.** `matchTitle`,
`toggleRestriction` and `sort` live in `node.properties` (right-click → Properties),
not in `widgets`. Use **`panel_set_property`**. `panel_set_widget` does not silently
half-work — it **refuses**, with `has no widget "matchTitle" (available: …)` listing
the widgets that do exist. That refusal is the fastest confirmation you are on the
properties path; read it rather than retrying the write.

**3. Fast Groups nodes take NO wiring and enumerate GROUPS by title.** Leave the
`OPT_CONNECTION` output unconnected. The node renders one toggle per matching group,
so **the groups must exist and be named before the node is useful**.

## Which rgthree nodes `panel_add_node` will actually add

The panel authorizes every add against fresh `/object_info` and **fails closed** on a
type it cannot find. Genuinely frontend-only types are exempt only via an explicit
allowlist (`FRONTEND_ONLY_NODE_TYPES`), so the exemption covers **seven** rgthree
types and no others:

| Frontend-only, `panel_add_node` WORKS | Frontend-only, `panel_add_node` REFUSES |
|---|---|
| `Fast Groups Bypasser (rgthree)` | `Bookmark (rgthree)` |
| `Fast Groups Muter (rgthree)` | `Mute / Bypass Relay (rgthree)` |
| `Fast Bypasser (rgthree)` | `Mute / Bypass Repeater (rgthree)` |
| `Fast Muter (rgthree)` | `Fast Actions Button (rgthree)` |
| `Node Collector (rgthree)` | `Random Unmuter (rgthree)` |
| `Label (rgthree)` | |
| `Reroute (rgthree)` | |

A refusal in the right-hand column is the guard working as designed, **not** a broken
pack and not something to work around — the node has no backend def and is not on the
allowlist. Say so and pick a different approach (the Bypasser/Muter cover almost every
real toggle need). Everything with a Python class — Power Lora Loader, Context*, Seed,
Any Switch, Power Prompt, Image Comparer — is a normal backend node and adds normally.

## Fast Groups Bypasser / Muter

`Fast Groups Bypasser (rgthree)` sets the nodes of a group to **bypass** (mode `4` —
the node is skipped and its input passes through). `Fast Groups Muter (rgthree)` sets
them to **mute** (mode `2` — the node does not execute and everything downstream
dies). **Prefer the Bypasser** for toggling an optional stage inside a chain; reach
for the Muter only when you genuinely want to stop a branch.

All of the following are node properties — set them with `panel_set_property`:

| Property | Values | Default | Notes |
|---|---|---|---|
| `matchTitle` | regex, case-insensitive | `""` | **Set this.** Empty means every group in the workflow becomes a toggle. It is a real regex matched *unanchored* against the group title, so anchor it (`^STAGE`) or it matches mid-title. |
| `matchColors` | comma-separated colors | `""` | Alternative filter; pairs with a color convention. |
| `toggleRestriction` | `default` / `max one` / `always one` | `default` | Both non-default values enforce mutual exclusion (they are matched on the substring `" one"`). Do NOT set either if the user may ever want all stages on in one queue. |
| `sort` | `position` / `alphanumeric` / `custom alphabet` | `position` | The default means **moving a group on the canvas silently reorders the toggles**. `alphanumeric` is stable — prefer it. |
| `customSortAlphabet` | string | `""` | Only read when `sort` is `custom alphabet`. |
| `showNav` | bool | `true` | Per-row jump-to-group arrow. |
| `showAllGraphs` | bool | `true` | Include groups that live inside subgraphs. |

### matchTitle does not rebuild the toggle list on the first write

`panel_set_property` stores `matchTitle` (and `matchColors` / `sort` / …) and
the reply `from`/`to` is truthful. Fast Groups nodes **do not implement
`onPropertyChanged`**. The toggle list is rebuilt by rgthree's
`refreshWidgets()` on a service tick (~8 ms after add, then every ~500 ms),
and leftover-row removal increments the index while splicing — a 22-group
list can stall at 13 with non-matching `Enable …` rows still present. A
never-drawn node can also come back as `widgets:{}` — that means the list
has **not been built yet**, not that there are no matching groups.
`panel_query_graph` also keys widgets by name, and every toggle is named
`RGTHREE_TOGGLE_AND_NAV`, so a built list collapses to one key.

**Do this:**

1. Set `matchTitle` **immediately** after `panel_add_node` (before the first
   unfiltered refresh paints every group).
2. Re-read with `panel_query_graph {ids:[<id>], fields:'detail'}`.
3. If `widgets` is empty **or** the canvas still shows `Enable` rows that do
   not match the regex, **set `matchTitle` again**. Do **not** delete and
   re-add the node — that is slower and still needs a second set.

### Recipe — make pipeline stages toggleable

1. `panel_create_group` per stage, with a **prefixed title** (`STAGE 1 — …`) so one
   anchored regex selects exactly the intended set.

2. **Verify group membership before you trust it.** Group membership is purely
   **geometric**: LiteGraph counts a node as a member when its *centre* falls inside
   the box, and the auto-fit box around your `node_ids` will happily swallow unrelated
   neighbours. When the live members differ from what you asked for, the result
   carries `extra_node_ids`, `missing_node_ids` and a `warning` alongside
   `requested_node_ids` — **read them**. (They appear only when you passed `node_ids`
   *and* something differs, so their absence is a real all-clear.)

   A stray node here is not cosmetic: toggling one stage will disable part of another.
   To fix it, **move the nodes apart** (`panel_edit_node`, or `panel_auto_layout`) so
   the regions are contiguous, or set an explicit `bounds` with `panel_edit_group` —
   then re-check. `panel_move_group` does **not** help: by default it drags the
   contained nodes along with the box, so the same nodes stay inside it.

3. `panel_add_node(class_type="Fast Groups Bypasser (rgthree)")`. Leave its output
   unwired. Add nodes one at a time, not as a parallel batch.

4. `panel_set_property` → `matchTitle` = `^STAGE`, and `sort` = `alphanumeric`.
   Set them immediately after the add, then re-read the node. If `widgets` is
   empty or leftover `Enable` rows remain, set `matchTitle` again — do not
   delete and re-add.

5. Toggle, then verify with `panel_graph_outline` — it tags nodes `[bypass]` / `[mute]`.

## Power Lora Loader (rgthree)

A **backend** node (present in `/object_info`) that stacks N LoRAs in one node. Each row
is a widget named **`lora_1`, `lora_2`, …** whose value is a composite object
`{on: bool, lora: "subdir\\name.safetensors", strength: float, strengthTwo: float|null}`
(`strengthTwo` is the separate CLIP strength, `null` in the simple view). Rows are
identified by the **presence of a `lora` key**, and the node's control widgets are
appended *after* them, so do not index positionally — **address the row by name**.

**Rows CAN be created programmatically.** A freshly added Power Lora Loader has
**no `lora_N` widgets at all** — the on-canvas "➕ Add Lora" button opens a chooser on a
mouse event that no panel tool can press, but `panel_set_widget` does not need it:
writing `lora_1` (then `lora_2`, …) with a **JSON object STRING** creates the row and
the reply carries `created_widget: "lora_1"`. Create rows **in order**, one call each:

```
panel_set_widget(node_id=<id>, widget="lora_1",
  value='{"on":true,"lora":"subdir/turbo.safetensors","strength":1,"strengthTwo":null}')

# A Windows subdir separator is a JSON escape — write it DOUBLED in the string:
#   "lora":"Anima\\Tools\\turbo.safetensors"
```

Re-read with `panel_query_graph {ids:[<id>], fields:'detail'}` to confirm the row
landed. If you need a stack from scratch, create `lora_1`, then `lora_2`, and so on;
do not assume a skipped number is the next row.

On an existing row, write ONE field with **dotted sub-field addressing** — it merges
onto the current object and preserves every other field:

```
panel_set_widget(node_id=<id>, widget="lora_1.strength", value=0.8)
panel_set_widget(node_id=<id>, widget="lora_2.on",       value=false)
panel_set_widget(node_id=<id>, widget="lora_1.lora",     value="style/foo.safetensors")
```

Writing a **bare scalar to `lora_1` itself** is the trap: it would set one field and
null the rest, so it is refused. To change several fields at once, pass a **JSON
object STRING** (the `value` argument accepts only string/number/boolean, so a literal
object fails tool validation before any write) — it is parsed and merged:

```
panel_set_widget(node_id=<id>, widget="lora_1", value='{"on":false,"strength":0.6}')
```

Fields are schema-checked: `on` non-nullable boolean, `strength` non-nullable number,
`lora` nullable string, `strengthTwo` nullable number. An unknown field name (a typo like
`lora_1.strenght`) is **refused**, not silently created, and nested paths are unsupported.

**Clearing a nullable field takes the JSON-string form, not a dotted write.** The panel
accepts `null` for `lora` / `strengthTwo`, but `value` is typed `string | number |
boolean`, so a bare `value=null` is rejected by tool-arg validation before any write
happens — the same schema limit as the whole-row case above. Clear it through the string:

```
panel_set_widget(node_id=<id>, widget="lora_1", value='{"lora":null}')
```

Turning a LoRA **off** (`lora_N.on = false`) is usually safer than clearing it anyway.

## Other commonly-seen rgthree nodes

- **Context / Context Big / Context Switch / Context Merge** — bundle
  MODEL/CLIP/VAE/conditioning into one `RGTHREE_CONTEXT` wire. **These are NOT virtual
  wiring.** Unlike Get/Set buses they are real executable backend nodes, so
  `panel_strip_workflow` and `panel_flatten_workflow` deliberately **keep** them — they
  run. Do not expect either tool to dissolve a Context chain. There is no hidden edge to
  resolve: every link is a real link, traceable with `panel_query_graph`. What a Context
  hides is *which field* a downstream node pulls out of the bundle, so read the chain
  node by node. (`panel_slice_workflow` still carves one pipeline out of a toggled
  monolith, and `panel_strip_workflow` still resolves any genuine Get/Set buses and
  Reroutes around it.)
- **Seed (rgthree)** — it **deletes the built-in `control_after_generate` widget** on
  creation, so do not try to write it; the widget is not there. Control is by SPECIAL
  SEED VALUES written to the `seed` widget instead: **`-1` randomize, `-2` increment,
  `-3` decrement**. Any concrete seed is returned **unchanged** — `42` stays `42` every
  run — so to pin a run, write the number; to re-randomize, write `-1`. The frontend
  resolves a special value into a real seed *before* queueing and shows the result in a
  read-only `last_seed` widget, so a `seed` re-read after a run may not be the special
  value the user set (a fixed seed, however, is stable).
- **Any Switch (rgthree)** — the first non-null input wins; a common A/B toggle paired
  with bypassed branches. An **empty Context counts as null**, so an unfilled Context
  branch is skipped rather than selected.

## Gotchas

- A **bypassed** node is skipped and passes its input through; a **muted** node kills
  everything downstream. Choosing the Muter where the Bypasser was meant breaks the
  chain rather than shortening it.
- Always `panel_graph_outline` before a run — a stale toggle is a top cause of a wrong
  render, and the outline marks `[bypass]` / `[mute]` explicitly.
- rgthree's `Bookmark` nodes respond to keypresses and are inert to agents.

## Sources

- **Official:** https://github.com/rgthree/rgthree-comfy
- **Empirical:** frontend-only allowlist and properties-not-widgets notes verified against the installed pack and the panel guard. `#1808` matchTitle rebuild: Fast Groups have no `onPropertyChanged`; `refreshWidgets()` leftover removal is `removeWidget(index++)` in `fast_groups_muter.ts`; first unfiltered tick is scheduled from `addFastGroupNode` in `fast_groups_service.ts`.
