Open-Source Wikis

/

ComfyUI

/

Systems

/

Prompt execution

comfyanonymous/ComfyUI

Prompt execution

How a JSON prompt becomes node outputs. This page covers the entire flow from POST /prompt to the WebSocket executed event.

Purpose

The prompt executor turns a graph description into a sequence of (node_class, inputs) → outputs calls, with caching so unchanged work is reused, validation so malformed prompts fail fast, and progress reporting so the user sees something happening.

Components

graph LR
    Client -->|POST /prompt| Server[server.PromptServer]
    Server -->|validate_prompt| Validator[execution.validate_prompt]
    Server -->|put| Q[server.PromptQueue]
    Q -->|get| Worker[main.prompt_worker thread]
    Worker -->|execute| PE[execution.PromptExecutor]
    PE --> Graph[comfy_execution.graph<br/>DynamicPrompt + ExecutionList]
    PE --> Cache[comfy_execution.caching<br/>CacheSet]
    PE --> Nodes[NODE_CLASS_MAPPINGS]
    PE -->|send_sync progress, executed| Server
    Server -->|ws| Client

The split between validate_prompt (synchronous, runs on the server thread) and PromptExecutor.execute (async, runs on the worker thread) is intentional: bad prompts get rejected before they consume a queue slot.

Data structures

Name File Role
prompt the JSON object posted to /prompt {node_id: {"class_type": "...", "inputs": {...}}}
DynamicPrompt comfy_execution/graph.py Mutable view; supports node expansion via ephemeral nodes
ExecutionList comfy_execution/graph.py DAG ready-set: yields nodes whose inputs are satisfied
CacheSet execution.py + comfy_execution/caching.py Holds outputs cache + objects cache (instances of node classes)
IsChangedCache execution.py Per-prompt memo of IS_CHANGED / fingerprint_inputs results
PromptQueue server.py Priority queue of pending prompts
PromptExecutor execution.py The orchestrator

Step by step

sequenceDiagram
    autonumber
    participant FE as Frontend
    participant Srv as PromptServer
    participant Q as PromptQueue
    participant W as prompt_worker
    participant PE as PromptExecutor
    participant N as Node

    FE->>Srv: POST /prompt
    Srv->>Srv: validate_prompt(prompt)
    Srv->>Q: put((number, prompt_id, prompt, extra_data, ...))
    Q-->>W: blocking get(timeout)
    W->>PE: e.execute(prompt, prompt_id, extra_data, sensitive)
    PE->>PE: build DynamicPrompt + ExecutionList
    loop ready node
        PE->>PE: cache key from inputs + IS_CHANGED
        alt cached
            PE-->>PE: reuse outputs
        else miss
            PE->>N: NODE_CLASS_MAPPINGS[type]() (cached in objects)
            PE->>N: get_input_data + _async_map_node_over_list
            N-->>PE: outputs (single or list)
            PE->>Srv: send_sync("executing", {node})
            PE->>Srv: send_sync("progress", {value, max})
            opt UI output
                N-->>PE: ui dict (images, gifs, …)
                PE->>Srv: send_sync("executed", history_result)
            end
        end
    end
    PE-->>W: history_result, success
    W->>Q: task_done(item_id, history_result, status)
    W->>Srv: send_sync("status", queue_remaining)

Output node selection: the executor finds nodes with OUTPUT_NODE = True and walks ancestors. Anything not connected to an output node is dead code and isn't run. Anything that gets cached doesn't run again until its inputs (or IS_CHANGED) change.

Caching

There are two caches in a CacheSet:

  • outputs — the actual returned values from a node run (what gets reused).
  • objects — the instantiated node objects themselves (what NODE_CLASS_MAPPINGS[type]() returned), so a node with expensive __init__ only initializes once.

Cache keys are derived two ways depending on the cache:

  • CacheKeySetID(node_id, class_type) — used for the objects cache where two nodes of the same class with different IDs should be distinct objects.
  • CacheKeySetInputSignature(class_type, frozenset_of_inputs) plus the IS_CHANGED value — used for the outputs cache where two nodes computing the same thing should share results.

The cache mode (HierarchicalCache, LRUCache, RAMPressureCache, NullCache) is selected at startup based on the --cache-* flags. See the comfy_execution package page for the full table.

Lazy inputs and IS_CHANGED

A node can declare an input as lazy: True. Such inputs aren't evaluated up front — instead, check_lazy_status (V1) or the V3 equivalent is called, returning a list of inputs that do need to be evaluated based on what's already known. The executor pauses the node, evaluates the requested inputs, and resumes.

IS_CHANGED is queried on every cache check. It can return a value or float("nan") to indicate "always re-run." The IsChangedCache memoizes results per prompt so a node with IS_CHANGED based on a file mtime doesn't stat the file twice.

Node expansion

A node can return {"expand": new_subgraph_json, "result": (...)}. The executor:

  1. Adds the new nodes as ephemeral children of the original node (DynamicPrompt.add_ephemeral_node).
  2. Re-builds the ready set.
  3. Runs the children before considering the original node "done."

This is how the Custom Sampler graph and similar composable nodes work without baking their structure into the executor.

Sensitive inputs

SENSITIVE_EXTRA_DATA_KEYS = ("auth_token_comfy_org", "api_key_comfy_org") (defined in execution.py). The server strips these from queue items returned to clients (_remove_sensitive_from_queue in server.py) but the executor injects them back into extra_data when running a node so API nodes can authenticate.

History

Each completed (or errored) prompt produces a history_result: a dict keyed by node ID containing the node's outputs plus any ui payload. The result is added to the in-memory history, accessible via GET /history/<prompt_id>. The executor also calls register_output_files (from the assets package) so written files become asset references when --enable-assets is on.

Integration points

  • Triggered by server.py on POST /prompt.
  • Runs on the prompt_worker thread set up in main.py.
  • Calls into the entire diffusion engine through node FUNCTION methods.
  • Streams events to the frontend through PromptServer.send_sync.
  • Plays with the Asset system via register_output_files.

Where to start a change

  • Adding a new cache mode: see comfy_execution/.
  • Tweaking how inputs are validated: validate_node_input in comfy_execution/validation.py.
  • New executor event: extend send_sync calls and the WebSocket message types; corresponding tests live in tests-unit/execution_test/ and tests/execution/.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Prompt execution – ComfyUI wiki | Factory