Skip to main content

Graph-health heuristics in workflow analysis

Status: implemented (this PR) · Implementation branch: spec/workflow-graph-health
Prior art: filliptm/ComfyUI_FL-MCP workflow_overview (web/js/query_executor.js) reports node-type histograms, disconnected nodes, and missing required inputs — but client-side with slot-name heuristics, because the live canvas lacks schema data. We run server-side with real /object_info required/optional data, keeping their heuristics only as a fallback for uninstalled node types.

Relationship to graph query (#169)

get_workflow (action:"query") / panel_query_graph (comfyui-mcp #179 + panel #77) is the generic surface — arbitrary filter/traverse/aggregate over a graph, agent-composed per question. Graph health is the curated counterpart: a fixed set of opinionated findings (disconnected, missing-required, duplicate loads, orphaned branches, muted/bypassed) with severities and remediation hints, computed server-side against /object_info. Health does not duplicate query capability; anything beyond these findings should be answered with the query tools rather than by growing this list.

Motivation

create_workflow (action:"validate") (src/tools/workflow-validate.tssrc/services/workflow-validator.ts) catches hard errors: unknown node types, missing required inputs, broken/self-referencing links, invalid output indices, out-of-list combo values. get_workflow (action:"analyze") (src/tools/workflow-library.ts:409-509) explains structure. Neither answers “is this graph healthy”: dead subgraphs, duplicate model loads wasting VRAM, sampler branches whose outputs never reach a save node, or muted/bypassed nodes silently dropping connections.

Placement: create_workflow (action:"validate") (primary), get_workflow (action:"analyze") view:"health" (secondary)

  • create_workflow (action:"validate") takes raw workflow JSON — the shape agents hold while authoring; health findings are a validation concern (severity-tagged issues) and compose with ValidationResult.issues.
  • get_workflow (action:"analyze") takes a library filename; extending its view enum with "health" reuses loadWorkflowApi() (which already converts UI→API via convertUiToApi) for near-zero extra code.

Tool API

create_workflow (action:"validate") — unchanged params plus:
get_workflow (action:"analyze")view enum gains "health".

New service: src/services/workflow-health.ts

Pure and synchronous — the caller (validator or analyze handler) already holds objectInfo; no extra fetch, trivially unit-testable.

Checks (API-format WorkflowJSON)

  1. Disconnected nodes (warning): build in/out adjacency from [nodeId, outIdx] input tuples; a node with no inbound links, no consumers, and not an output_node per object_info is isolated.
  2. Missing required inputs (warning): primary source objectInfo[class_type].input.required. Fallback heuristic only when the class is absent from object_info (uninstalled custom node): FL-MCP’s isInputSlotRequired rules — known-optional slot-name list; Loader/Load families require *name*/*path*/*ckpt*; Sampler families require model/positive/negative/latent_image; VAE families require samples/images/vae; default required. Tagged heuristic: true.
  3. Duplicate model loads (warning): group nodes by (class_type, model-file widget value) where class_type matches /Loader|Load/ and the value matches the model-file regex already in workflow-validator.ts:213 (/\.(safetensors|gguf|ckpt|pt|pth|bin|sft)$/i). ≥2 nodes on the same file → one finding listing all node ids.
  4. Orphaned branches / no output reachable (warning): reverse-BFS from every output node (objectInfo[ct].output_node === true plus the hardcoded SaveImage/PreviewImage/SaveAnimated* list at workflow-validator.ts:137-146). Unreached non-output nodes are “computed but never saved” — reported as one finding per connected component (avoids 40 line items on big graphs). The existing zero-output validator check stays as-is.
  5. Muted/bypassed (info): via _meta.mode where present. Known limitation, documented: convertUiToApi drops mode-2/4 nodes (comment at workflow-converter.ts:731; the _meta.mode: "muted"|"bypassed" mapping lives at 932-942), so for get_workflow (action:"analyze") we surface the converter’s existing warnings instead; raw API JSON handed to create_workflow (action:"validate") is checked directly, and silently skipped when _meta is absent.

Output composition

create_workflow (action:"validate") text output gains, after Errors/Warnings:
Programmatically: findings are appended into result.issues (severity warning/info) so existing consumers see them; ValidationIssue.severity widens to "error" | "warning" | "info" and gains optional kind?: string; ValidationResult gains health: GraphHealth. valid remains errors-only — health never flips validity.

Implementation plan

  1. src/services/workflow-health.tsanalyzeGraphHealth (~180 lines, pure). Types imported from src/comfyui/types.ts.
  2. src/services/workflow-validator.ts — widen ValidationIssue.severity; call analyzeGraphHealth after existing step 4 (objectInfo already in hand); merge findings; return health.
  3. src/tools/workflow-validate.tshealth param; render the section incl. the info bucket.
  4. src/tools/workflow-library.ts — add "health" to get_workflow (action:"analyze")’s view enum; handler calls analyzeGraphHealth(workflow, objectInfo) (both in scope at lines 443-444) and renders.

Test plan (vitest)

New src/__tests__/services/workflow-health.test.ts — pure-function tests with a small mocked ObjectInfo (pattern: workflow-converter.test.ts): isolated node; duplicate checkpoint; orphaned upscale branch; heuristic fallback for an unknown Sampler-family class missing model; _meta.mode info; per-component grouping. Plus one validator test asserting health findings merge without affecting valid.

Rollout / compat

Non-breaking: default health: true adds text but never errors (flip the default to false later if token cost matters in compact mode). No config. Regenerate tool docs via npm run docs:gen.