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 s

SKILL.md

GitHub

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:

{
  "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:

{
  "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

{
  "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:

{
  "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

{
  "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.