# Figma Sub Agents

> Orchestrator guide for delegating Figma MCP phases to specialized sub-agents. Use when a Figma task is large enough to risk context overflow — component sets with 8+ variants, unknown tree depth, or sessions expected to exceed 100 tool calls. Supports both serial discovery and parallel build/style phases.

- Skill: `dabowman/figma-sub-agents` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dabowman/figma-sub-agents`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dabowman/figma-sub-agents/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: dabowman (https://skillmd.com/u/dabowman)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/dabowman/figma-sub-agents

---


# Figma MCP Sub-Agent Orchestration

Large Figma sessions hit three problems in a single-agent context: **context pressure** (large node tree responses), **attention drift** (losing track of which nodes are done after 30+ sequential calls), and **error pollution** (9 retries of a failing tool consuming planning context). Sub-agents solve this by giving each phase its own clean context window.

The primary tool for reading nodes is **`read`**, which returns structured YAML (FSGN format) with deduplicated variable/style/component defs and a `tokenEstimate` in the meta. It accepts `nodeId` (single) or `nodeIds` (multiple, fetched in parallel).

Sub-agents also enable **parallel execution — on the plugin transport only**: multiple agents can modify different parts of the Figma document simultaneously, with plugin-level concurrency control ensuring safety. **On the remote transport (`FIGMA_TRANSPORT=remote`) the MCP server serializes every command per file (FIFO queue), so parallel sub-agents do NOT increase throughput.** Sub-agents still pay off on remote for context isolation — just launch Build/Style agents serially instead of in parallel.

**Available sub-agents:**
- **Discovery** (`figma-discovery` agent) — read-only exploration, always runs serial
- **Builder** (general-purpose agent) — creates/clones node structures, can run in parallel
- **Styler** (general-purpose agent) — applies variables and text styles, can run in parallel

---

## When to Use Sub-Agents

### Discovery — delegate when **any** of these are true:
- Target component set has **8+ variants**
- Frame tree depth is unknown or likely > 4 levels
- This is the first time seeing this Figma file in the session
- A `read` response has `tokenEstimate > 8000` even at `detail=structure`
- You need both a full text node inventory AND a variable binding audit in the same pass

**Skip it when** you already have the node IDs and structure, the target has < 20 children, or you only need one piece of info (just call the tool directly).

### Builder/Styler — delegate when:
- Build spec has **5+ nodes** to create or clone
- Binding plan has **20+ variable bindings** or text style assignments
- Work can be partitioned into **independent node subtrees** (different variants, different sections)
- You want to parallelize to reduce wall-clock time (plugin transport only — remote serializes per file)

---

## Execution Protocol

### Channel Setup (always do this first)

1. **Orchestrator joins the channel first.** Call `use_file` (no args) before spawning any sub-agent.
2. **Pass the channel name explicitly** in every sub-agent prompt. Do not let sub-agents auto-discover — this avoids race conditions. (Remote transport: there are no channels — `use_file` takes a Figma URL or fileKey; pass that same fileKey to every sub-agent.)
3. **Check `status` first** in every sub-agent result — if `"blocked"`, surface the error to the user and stop.

### Serial Phases (must be in order)

```
Discovery → Planning → Building → Styling → Verification
```

Discovery always runs alone. Planning happens in the orchestrator. Verification happens in the orchestrator.

### Parallel Execution (within Building or Styling phases — plugin transport only)

The Figma plugin has concurrency control that makes parallel agent execution safe:
- **Node-level write locks** prevent two agents from writing to the same node simultaneously
- **Global mutex** serializes tree-mutation operations (the `create`, `delete_multiple_nodes`, etc. wire commands)
- **Concurrency cap** (max 6 in-flight operations) prevents Figma CPU budget exhaustion

**Remote transport**: none of this applies — the server's per-file FIFO queue serializes every command, so parallel agents just wait in line. On remote, run Build/Style agents one at a time and keep the partitioning (it still bounds each agent's context).

**Rules for parallel sub-agents:**

1. **Partition by node subtree.** Each agent operates on a **disjoint set of nodes**. Partition by variant, by section, or by component — never assign the same node to two agents.
2. **Don't mix phases.** Don't run a Builder and Styler in parallel — build everything first, then style everything. The Styler needs the nodes to exist before it can bind variables.
3. **Use `run_in_background: true`** on the Agent tool to launch parallel agents. You will be notified when each completes.
4. **All agents share one channel.** Sub-agents share the parent's MCP server and WebSocket connection. Request ID correlation handles response routing — no multi-channel needed.
5. **Verify after parallel phases.** After all parallel agents complete, call `read(nodeId, detail="structure")` on the parent to confirm the expected structure.
6. **Max 3 parallel agents** for build/style phases. More than 3 creates diminishing returns and risks hitting the plugin's concurrency cap (6 in-flight operations, ~2 per agent).

### Partitioning Strategies

**By variant** (most common): Each agent handles a disjoint set of variants within a component set.
```
Agent A: Build/style variants for State=Loading  (nodes A1-A5)
Agent B: Build/style variants for State=Empty    (nodes B1-B5)
Agent C: Build/style variants for State=Selection (nodes C1-C5)
```

**By section**: Each agent handles a different top-level section of the page.
```
Agent A: Build Header component set
Agent B: Build Sidebar component set
Agent C: Build Footer component set
```

**By operation type** (for styling): Each agent handles a different type of binding.
```
Agent A: Bind all color variables
Agent B: Apply all text styles
Agent C: Bind all spacing/radius variables
```
Note: this only works if each node gets only ONE type of binding per agent. If a node needs both a color variable and a text style, assign that node to ONE agent that does both.

---

## Discovery Sub-Agent

The agent definition ships in this plugin at `agents/figma-discovery.md`. It has:
- A read-only tool set (no create/modify tools)
- A system prompt with its full workflow and output schema

**Tools available to the agent:** `use_file`, `read`, `grep`, `get_local_components`, `get_design_system`. All tools are declared in the agent definition and loaded automatically — no ToolSearch needed. Note: `read` includes component metadata in `defs.components`, and `get_design_system` covers both variables and styles.

### Spawning the Agent

Use the Agent tool with `subagent_type: "figma-discovery"`. The prompt only needs task-specific parameters — no system prompt needed.

```
Agent(
  subagent_type: "figma-discovery",
  description: "Discover <component name> structure",
  prompt: JSON.stringify({
    channelName: "<from your use_file call>",
    nodeId: "<target component set or frame ID>",
    description: "Map DataViews component set",
    include: ["text_nodes", "variables", "text_styles"],
    nameFilter: "DataRow"   // omit if not filtering components
  })
)
```

Valid `include` values: `text_nodes`, `variables`, `text_styles`, `components`.

### Using the Result

The agent's final message is JSON. Parse it immediately:

```
const discovery = JSON.parse(agentResult);

if (discovery.status === "blocked") {
  // Surface to user: discovery.error + discovery.recommendation
  // Do NOT proceed to build/style phases
} else {
  // discovery.component_set.variants[].id → parentId values for create/clone calls
  // discovery.component_sets_in_frame     → all component sets when target is a FRAME (pick one to deep-map)
  // discovery.text_nodes[]                → input for edit (variables, textStyleId)
  // discovery.unbound_nodes               → if >= 20, a Styler phase is needed; null = unknown
  // discovery.variables                   → sanity-check tokens are loaded
  // discovery.summary                     → user-facing status message
  //
  // Variant children now include:
  //   layoutMode       → auto-layout direction (if active)
  //   boundVariables   → list of bound field names (e.g. ["fill", "cornerRadius"])
  //   componentName/Id → resolved for INSTANCE nodes via defs.components in FSGN
}
```

### Output Schema Reference

**Success:**
```json
{
  "status": "success",
  "component_sets_in_frame": [
    { "id": "...", "name": "DataViews", "type": "COMPONENT_SET", "variantCount": 16 },
    { "id": "...", "name": "DataForm", "type": "COMPONENT_SET", "variantCount": 2 }
  ],
  "component_set": {
    "id": "...",
    "name": "...",
    "variant_properties": ["Layout", "State"],
    "variants": [
      {
        "id": "...",
        "name": "Layout=List, State=Default",
        "children": [
          { "id": "...", "name": "Header", "type": "FRAME", "layoutMode": "HORIZONTAL", "boundVariables": ["fill", "cornerRadius"] },
          { "id": "...", "name": "Row 1", "type": "INSTANCE", "componentName": "_Dataviews/Table/Row", "componentId": "2254:11156", "boundVariables": [] }
        ]
      }
    ]
  },
  "text_nodes": [
    { "id": "...", "name": "Title", "parentVariantId": "16547:36681", "content": "Activity", "style": "Heading MD", "fills_variable": null }
  ],
  "variables": {
    "collections": ["Primitives", "Semantic"],
    "total_count": 84,
    "by_collection": {
      "Semantic": [
        { "id": "VariableID:15613:5786", "name": "gray-700", "type": "COLOR" },
        { "id": "VariableID:15613:5784", "name": "surface-primary", "type": "COLOR" }
      ]
    }
  },
  "text_styles": [
    { "name": "Heading MD", "id": "S:5a04abc..." },
    { "name": "Body SM", "id": "S:7b12def..." }
  ],
  "unbound_nodes": 47,
  "summary": "4 variants exist. 47 nodes have no variable bindings. 12 text nodes have no text style."
}
```

**Blocked:**
```json
{
  "status": "blocked",
  "error": "read timed out twice",
  "last_tool": "read",
  "recommendation": "Call use_file again — connection may have dropped"
}
```

---

## Builder Sub-Agent

Creates or clones node structures. Uses general-purpose agent type (not a custom agent definition — the prompt contains all instructions).

### Spawning

```
Agent(
  description: "Build [description] variants",
  run_in_background: true,  // for parallel execution
  prompt: `You are a Figma Builder agent. The WebSocket channel is already joined —
call use_file with channelName "${channelName}" as your first action.

YOUR ASSIGNED NODES (do NOT touch anything outside this list):
${JSON.stringify(assignedNodes)}

WHAT TO BUILD:
${buildSpec}

RULES:
- Use write for complex structures (reduces many calls to 1)
- Use write with fromNodeId when duplicating existing patterns
- Use write with type="INSTANCE" and componentId for reusing library parts
- write responses include the created structure plus a warnings block (100px balloons, FILL-not-applied, font fallback, overlaps) — act on warnings; only read(nodeId, detail="structure") back when warnings or the returned structure suggest a mismatch
- Return JSON: {"status": "success", "created_nodes": [...ids], "summary": "..."}
- If any tool fails twice on the same call, stop and return: {"status": "blocked", "error": "...", "last_tool": "...", "recommendation": "..."}
`
)
```

### When to Parallelize Builders

- Multiple independent variants to create (e.g., 4 State variants each with the same structure)
- Multiple independent component sets to build
- Large `write` specs that don't share parent nodes

### Output

```json
{
  "status": "success",
  "created_nodes": ["16547:36700", "16547:36701", "16547:36702"],
  "summary": "Created 3 Loading state variants with 4 children each"
}
```

---

## Styler Sub-Agent

Applies variable bindings and text styles. Uses general-purpose agent type.

### Pre-flight with read

Before spawning Styler agents, the orchestrator can call `read(nodeId, detail="full")` on the built subtree. The FSGN `defs` section lists all variables and styles already present; `variableBindings` on each node shows what's already bound. This makes the binding plan explicit — pass it directly to the Styler via `VARIABLE BINDINGS TO APPLY`.

### Spawning

```
Agent(
  description: "Style [description] variants",
  run_in_background: true,  // for parallel execution
  prompt: `You are a Figma Styler agent. The WebSocket channel is already joined —
call use_file with channelName "${channelName}" as your first action.

YOUR ASSIGNED NODES (do NOT touch anything outside this list):
${JSON.stringify(assignedNodes)}

VARIABLE BINDINGS TO APPLY:
${JSON.stringify(bindings)}

TEXT STYLE ASSIGNMENTS:
${JSON.stringify(textStyles)}

RULES:
- Use edit() with variables field to bind design tokens to node properties (supports flat list or nested tree)
- Use edit() with textStyleId to apply text styles (deduplicates font loading automatically)
- Process in order: variable bindings first, then text styles
- edit responses report per-op failures and warnings (scope-mismatched binds are skipped with a warning) — act on those; spot-check ONE node with read(nodeId, detail="full") only if a result looks off
- Return JSON: {"status": "success", "bindings_applied": N, "styles_applied": N, "summary": "..."}
- If any tool fails twice on the same call, stop and return blocked status
`
)
```

### When to Parallelize Stylers

- 40+ bindings to apply across different node subtrees
- Multiple variants that each need independent styling
- Different sections of a page with no shared nodes

### Binding Plan Format

The orchestrator prepares a binding plan from Discovery output and passes it to each Styler:

```json
{
  "bindings": [
    { "nodeId": "16547:36700", "field": "fill", "variableId": "VariableID:15613:5786" },
    { "nodeId": "16547:36701", "field": "cornerRadius", "variableId": "VariableID:15613:5800" }
  ],
  "textStyles": [
    { "nodeId": "16547:36710", "styleId": "S:5a04abc..." },
    { "nodeId": "16547:36711", "styleId": "S:7b12def..." }
  ]
}
```

### Output

```json
{
  "status": "success",
  "bindings_applied": 22,
  "styles_applied": 8,
  "summary": "Applied 22 variable bindings and 8 text styles to Loading variants"
}
```

---

## Parallel Session Example

```
TIME    ORCHESTRATOR              BUILDER-A             BUILDER-B
─────   ────────────              ─────────             ─────────
0:00    use_file
0:01    → Discovery agent
0:03    ← discovery JSON
0:04    Plan: partition by State
0:05    → Builder A (Loading)     write ──►
0:05    → Builder B (Empty)                             write ──►
        [both run_in_background]
0:07                              ◄── done              ◄── done
0:08    verify structure

TIME    ORCHESTRATOR              STYLER-A              STYLER-B
─────   ────────────              ────────              ────────
0:09    → Styler A (Loading)      edit ──►
0:09    → Styler B (Empty)                              edit ──►
        [both run_in_background]
0:11                              ◄── done              ◄── done
0:12    verify bindings
0:13    Report to user
```

**Estimated speedup (plugin transport):** For a 16-variant component set with 130+ variable bindings:
- Serial: ~36 minutes
- Parallel (3 agents): ~19 minutes (~1.9x speedup)

The speedup scales with workload size. Design system builds with 50+ components see larger gains because build and style phases dominate. On the remote transport expect NO parallel speedup (per-file FIFO) — the win there is fewer calls per task and context isolation.

---

**Post-session**: After completing a large Figma session (50+ tool calls), run `/analyze-session` to capture efficiency metrics, error patterns, and improvement recommendations.

