The Graph Has a Contract

This chapter examines the contract underneath a ComfyUI workflow: how nodes, literal values, links, and declared types turn a canvas into executable data. The practical goal is to read a node chain and predict whether it can run before a model is loaded. Once the contract is clear, a connection is no longer just a wire between boxes; it is a promise about what value crosses a boundary, who supplies it, and what can happen when the promise is not met.

A workflow is dataflow, not canvas coordinates

A ComfyUI workflow is easiest to understand as a dataflow description. Each node identifies a kind of operation and carries an input mapping. The mapping may contain a literal value, such as a seed or an image filename, or it may contain a link to another node's output slot. The link is the essential relationship: it says that this input will be supplied by the result produced elsewhere in the graph.

That sounds abstract until the graph is compared with a familiar generation task. A text-encoding node may receive a literal string such as a description and a linked text-encoder model. A sampler may receive links to a model, positive and negative conditioning, and a latent, while its seed, step count, guidance scale, sampler choice, and schedule remain literal settings. A decoder then receives a latent link and a VAE link. The canvas shows these relations spatially, but the executable meaning comes from the values and links, not from the fact that one box sits to the left of another.

This distinction matters because a workflow need not be linear. One model output can feed several downstream operations. One conditioning result can be compared across two samplers. A single image can lead to a preview and a saved output. A graph can also merge branches when an operation accepts several inputs. The visible arrangement helps a person keep track of those choices, but the backend can determine dependency from the link itself. A nearby node that has no path to a selected output is not automatically part of the computation.

The link has two pieces of information: an upstream node identifier and an output index. The index is important because a node can expose more than one result. A loader, for example, can return a diffusion model, a text encoder, and a VAE. Connecting the first result to a text-encoding input would not be a harmless variation of the same idea; it would cross a different interface. The graph therefore records not only “use this node,” but “use this particular value from this node.”

Node identifiers provide identity without becoming the meaning of the workflow. ComfyUI's graph-building utility prefixes generated identifiers so that nodes created in separate graph fragments do not collide. That is an implementation detail with a practical consequence: a reusable piece of graph can be inserted into a larger execution without silently referring to a node that already exists there. The identifier names the producer; the declared input and output contracts explain what the producer means.

The same principle lets a graph branch for comparison. Imagine holding the model, text conditioning, and starting latent constant while sending them to two samplers with different guidance values. The shared links describe the controlled part of the comparison, while the separate literal values identify the changed decision. Alternatively, two model branches can feed later stages that remain common. The graph does not judge whether the comparison is artistically or scientifically fair, but it makes the chosen variables visible instead of leaving them in a person's memory.

This is why a workflow should not be read as a screenshot of a process. It is a set of named relationships. The first questions are: What output matters? Which input supplies it? What upstream result is linked there? Is that result the type the consumer expects? Following those questions reveals the computational shape even when the canvas is large or visually untidy.

A graph can still contain ambiguity that the contract does not solve. A custom node may perform a complicated model operation behind one output, or attach meaning to metadata that a general type name cannot express. Two values may share a broad type while representing different assumptions. The contract narrows the space of valid connections; it does not replace the node author's responsibility to make the declared meaning true.

Types, optionality, and the boundary of execution

A node contract begins with its inputs and outputs. In the established node interface, INPUT_TYPES describes required, optional, and hidden inputs. The node also declares its return types and the function that performs its operation. The V3 interface expresses the same idea through a schema containing input and output objects, with additional flags for such behavior as laziness, list processing, graph expansion, and output status. These are not decorative descriptions. The server uses them to expose node information and the executor uses them to decide how values can cross boundaries.

The type vocabulary gives a newcomer a useful map of a generation workflow. MODEL refers to the denoising model consumed by sampling. CLIP identifies a text-encoding component. CONDITIONING carries encoded guidance. LATENT carries the representation that sampling transforms. VAE performs the conversion between pixel-space material and latent-space material. IMAGE carries pixel data. MASK can constrain an image or conditioning operation. The names are not interchangeable labels for “some data.” They indicate which stage a value belongs to and what kind of operation can sensibly receive it.

A declared type makes composition checkable. If a node expects an image and receives a latent, the connection is suspect before any expensive computation begins. If a node accepts a union such as an integer or a float, the interface can express that flexibility without creating a separate node for every numeric representation. ComfyUI's validation also recognizes an ANY type and other compatibility behavior. That is useful for deliberately generic nodes, but it weakens what can be inferred from the socket alone. A flexible connection may be structurally accepted while still requiring the node implementation to understand the value it receives.

The difference between structural compatibility and semantic compatibility is one of the graph's most important boundaries. Two objects can both be represented by a broad category and still carry different assumptions about shape, device, batch size, or model family. Conversely, a union type can be exactly the right interface when a node genuinely handles several representations. The type system can prevent obvious category errors; it cannot prove that every model-specific convention has been respected.

The contract also gives the frontend and backend a common description of a node. Input metadata can describe a default, numeric limits, a multiline text field, a tooltip, or whether an input should be shown as a socket. Output metadata can name and describe result slots. The older node interface exposes these properties through class attributes and an input-description method. The newer interface gathers them in a schema that is finalized and validated before the node is registered. In both cases, the goal is the same: the editor can present a meaningful control, while the executor can call the node with values that match its declared shape.

That shared description is what allows a visual change to remain an executable change. If a user replaces a literal value in a widget, the graph data changes. If they connect a socket, the input changes from a literal to a link. If a node has more than one output, selecting a different socket changes the downstream value without changing the upstream operation. The graph does not need a separate handwritten explanation for every possible arrangement; the contract supplies the common language that makes arrangements discoverable and checkable.

The contract is also a boundary for ownership. The node declares what it needs and what it returns. The graph supplies literals and links. The validator checks whether the supplied shape is admissible. The executor later evaluates the upstream link and calls the node's named function. A node may use hidden prompt or identity information, but that information still arrives through an explicit part of the contract. Keeping these responsibilities separate matters when something fails: an absent required input points to graph completeness, a type mismatch points to an interface, and an exception inside the function points to the node's implementation or its model assumptions.

ComfyUI's coexistence of older and newer node interfaces makes this boundary visible. A V1 node describes its inputs and return types with established class attributes. A V3 node defines a schema and returns a standardized output object that can carry results, UI data, or an expansion graph. The two styles are different ways to express the same architectural role, not two unrelated execution models. A workflow reader does not need to choose an API version to understand the essential question: what inputs does this operation accept, what outputs does it expose, and what extra behavior does it declare? The coexistence does mean that a workflow or extension can depend on a particular contract surface, so “a node exists” is not by itself proof that another installation can load it.

Required and optional inputs express another distinction. A required input is part of the minimum information needed for the node's operation. An optional input lets the node offer an alternate path or enhancement without forcing every workflow to provide it. This is more than a user-interface convenience. It changes whether a graph is complete enough to execute and whether an absent value should be interpreted as a deliberate default, a missing capability, or a validation error.

Hidden inputs sit at a different boundary. They support information that the ordinary visual connection does not need to expose in the same way, such as the complete prompt, extra metadata, a unique node identifier, or a dynamic prompt object. An output node can receive prompt and metadata information so that it can save a record alongside its file. A node can receive its identity for client-server communication. A hidden input is therefore still part of the contract, even when a user does not see it as a normal socket or widget.

That separation helps explain why a saved image can carry more than pixels. The output operation can receive the prompt that produced the image and additional information supplied for persistence. The graph remains the visible computation, while hidden data connects it to the record of that computation. The two layers should not be confused: metadata can help recover a workflow, but it does not turn the image into the model weights or into a guarantee that another runtime will produce identical pixels.

The output-node flag establishes the contract's connection to execution. By default, a node is not considered an output. Marking a node as an output tells ComfyUI that the inputs required to produce that result are relevant to the submitted prompt. This is why a graph may contain a useful-looking branch that never runs: if no selected output depends on it, it is not necessarily part of the requested computation. The declaration gives the executor a root from which it can discover the required ancestors.

That rule is a design choice with both benefits and costs. It avoids treating every object on a large canvas as an instruction to run, which is important when a workflow contains experiments, alternate branches, or intermediate views. It also means that a node author who forgets to mark a real output can make an apparently complete path irrelevant to execution. Users therefore need to ask not only whether a node has the right type, but whether the desired result is attached to an execution-facing root.

The contract's structure can be summarized as a set of relationships rather than a list of class names:

classDiagram class Workflow class Node class Input class Output class Link class Type class OutputNode Workflow "1" o-- "*" Node : contains Node "1" o-- "*" Input : declares Node "1" o-- "*" Output : exposes Link --> Output : reads from Link --> Input : connects to Input --> Type : expects Output --> Type : returns Node <|-- OutputNode : may be marked

The diagram makes the ownership clear. A workflow contains nodes. Nodes declare what they accept and expose. A link does not carry an arbitrary promise; it selects an output and supplies it to an input. Types describe the boundary on both sides. An output node is a node with an additional role in deciding which part of the graph is relevant.

These relationships also explain common failure surfaces. A required input may be absent. A link may point to an output whose declared type does not overlap the consumer's accepted type. A node may be missing altogether, or a graph may contain a cycle that cannot provide a first value. A literal value may be outside the range declared by the input. Such failures are not evidence that the canvas is merely being fussy. They are signs that the executable data does not satisfy the agreement its nodes declared.

The contract is not a complete description of quality. A graph can pass structural validation and still fail when a checkpoint lacks a usable text encoder, a VAE cannot decode a particular latent shape, or a custom node makes an unsupported assumption. But validation moves many failures to an earlier, clearer boundary. It lets ComfyUI reject a malformed request before treating it as a model execution problem.

Once the graph is understood as a set of contracts, pressing Queue becomes a traceable event rather than a mystery. The server can inspect the submitted data, follow links from output roots, and report which agreement failed if the request is invalid. The next question is how that inspection becomes an actual schedule: which dependencies are selected, which nodes become ready, and how the queue and executor carry the graph through to a result.

References