The engine never stores a workflow. Every ComfyUI /promptis assembled at generate time from library content — atoms for the nodes, compounds and workflows for the order, protons for the types. This walks one real composition,ltx_i2v_clip (LTX 2.3), a stage at a time. The numbers are from running the composer on it: 46 nodes, six under s1_, 39 unders2_, plus one loader.
The current default, ltx25_i2v_clip (LTX 2.5), works the same way but is shaped differently: its models live inside the atom, and asave_video member ends the chain, so the graph carries its own output node.
Port colours match the Library's port chips: the same slot type looks the same in both places. Colour is never the only signal — every port is labelled with its type.
Fetched from the MX Library
Read from Postgres — the runtime source of truth. An agent never reads the library from GitHub.
- compoundltx_i2v_clip
the ordered member list
- atomltx_models
a ComfyUI node block + exit slots
- atomltx_i2v
a node block + sentinels + exit slots
- protonone per node class
per-slot TYPES only — no graph content (the type index)
Nothing pre-authored is loaded. There is no stored workflow JSON anywhere in this picture — the graph below exists only for this one request.
The request
generate takes exactly ONE root — an atom id or a compound id.
Here the agent asks for the compound ltx_i2v_clip, with an image as an input (an asset edge) and a prompt as a param. Nothing else is named: the rest is worked out from the library. A disabled library item is refused before anything composes. Inside a plan, every ComfyUI step arrives through this same call.
The four entity kinds
- workflow
- Top of the hierarchy. An ordered
memberslist, end to end. Contributes order, not nodes. A pure ComfyUI chain flattens into one graph; a workflow whose members include agent, service or touchpoint steps, or that carries a## Planblock, is run as a plan instead (see below). - compound
- The same shape, one level down, and it can itself provide a capability. Flattened recursively into the atom chain. It can declare its own
suggested_defaults. - atom
- A
comfyatom is the only kind carrying real ComfyUI nodes — a machine-generated JSON block whose dangling inputs are__INPUT_<name>__sentinels, plus theexit_slotsit exposes. Atoms also have aruntime:http(a microservice, called withcall_service) oragent(the model itself does the step). Those carry no graph, so the composer skips them by design andgeneraterefuses them. - proton
- One ComfyUI node class, derived from the host's
/object_info. Carries no graph content at all — only the per-slot types, which is what lets the composer wire by type rather than by hope. A class with no proton is invisible to type wiring, so a missing proton surfaces as an "unresolved sentinel", not as a broken atom.
A compound is not a plan
Everything above describes composing one ComfyUI/prompt. A compound flattens its members into exactly that: one graph, wired across members, containing whatever save node the chain ends in. A plan is different — the plan engine runs each step as its own prompt, and passes an artifact from one to the next.
That distinction has a sharp edge. ComfyUI refuses any prompt with no output node, so an atom that is a pure transform — image in, image out, nothing saved — is submittable inside a compound but not on its own. A plan step whose graph has no output node therefore gets aSaveImage appended, wired to its IMAGE exit slot (MIL-524). That is also what makes the next step possible: a step that saves nothing produces no artifact to consume.
Not everything is a graph: plans
Most real work mixes ComfyUI renders with things that are not graphs — an agent step that writes a concept or looks at frames, a service call (voice-over, transcription, a render service), a question for the user. The plan engine runs those as an ordered list of steps over one shared context. A step is either an atom (of any runtime) or a touchpoint — confirmation, choice, text input, asset upload, review/edit, or selection — which pauses the plan until it is answered.
- Wiring. A step's
inputscan reference an artifact an earlier step produced (fromStep), or one shot of an approved storyboard. Steps declare what theyprovidesandconsumes, so the engine knows which steps depend on which keys. - Conditions.
onlyIfruns a step only when a touchpoint was answered a certain way (or per shot, so a mixed film routes footage shots and animation shots to different members). A step that does not run can hand the artifacts of a named key straight on, so everything downstream reads one key either way. - Failure. A failed atom step becomes a recovery question — retry, skip or edit params — rather than ending the run.
- Saving. A finished run can be promoted to a workflow. Since MIL-1016 that keeps the plan itself (wiring, questions,
onlyIf), so the saved workflow replays; a run that was already expanded from a workflow is refused, because its plan is in the library already.
Living Graph still holds inside it: each ComfyUI step is composed fresh, exactly as above.check_composition reports a workflow like this as not_applicable(plan-not-graph), not broken — there is no single graph to check.
How the agent drives all of this
The agent has no file, shell or GitHub tools and never writes a ComfyUI graph. It has library tools (list_atoms, search_atoms, read_item,check_composition), generate, the plan tools, the touchpoint tools, and a few for services, images and characters. Which one it reaches for is a ladder:
- One
generatecall with one root — an atom or, preferably, a compound that already covers the request. This is the default. plan_from_workflowwhen the workflow declares a## Planblock: the engine writes the wiring, so the agent never hand-writes afromStep.propose_planonly for a workflow with no declared plan, or a one-off chain, where one render must finish before the next.call_serviceforhttpatoms andcall_agentforagentatoms —generateanswersnot_composablefor both.
The generate tool checks before it composes: parameters against the item (invalid_params), plus preflights for talking-head and MiniMax H3 items. A missing input comes back as missing_inputs, steering the agent to ask the user (ask_param or request_upload) rather than invent a value. After a render it lists the artifacts as refs to pass straight back in, and aNOT APPLIED line names any override the item has no input for — the render used its defaults for them.
Inside a plan the same render goes through the plan bridge, which calls the generate port directly — so those tool-level preflights do not run for plan steps. Plan wiring mistakes (afromStep written as a string, or pointing at a touchpoint instead of the render) are refused when that step is reached, not when the plan is proposed. Questions to the user go through the touchpoint tools (confirm, choose, ask_text,ask_param, request_upload, review_edit,select); only an asset_upload can be pre-answered, because the others ask the user to decide something.
Promoting a run to a workflow is not an agent tool: it is a Control Plane operation, offered from the Agents Manager.
Source of truth for this page: packages/composer(Composer.compose / composeCompound / wire.ts),packages/plan-engine, the harness's generate adapter and tools (apps/harness/src) and the orchestrator prompt. If the composer's behaviour changes, this page changes with it.