Workflow Node Versioning#
Dify's workflow engine supports multiple backend implementations of the same logical node type, disambiguated by a version string. This allows legacy and new variants to coexist in the registry without renaming the underlying node type enum value.
Backend: Node Registry & Version Resolution#
The registry is a two-level map: NodeType β { version_string β Node class }. It lives in the Node base class (from the graphon library) and is bootstrapped once at startup by register_nodes(), which recursively imports:
graphon.nodesβ built-in node implementationscore.workflow.nodesβ Dify-specific overrides and extensions
Import-time side effects cause each node class to self-register. A node declares its type and version via two class-level attributes:
| Class | node_type | version() return | Module |
|---|---|---|---|
AgentNode (legacy) | BuiltinNodeTypes.AGENT | "1" | nodes/agent/agent_node.py |
DifyAgentNode (V2) | BuiltinNodeTypes.AGENT | "2" | nodes/agent_v2/agent_node.py |
Both classes share BuiltinNodeTypes.AGENT as node_type β the version string plus an optional discriminator differentiate them.
Version resolution at execution time is handled by resolve_workflow_node_class():
- Look up the
node_typein the registry. - For Agent nodes with
version="2", check for the presence ofagent_node_kind="dify_agent":- If present, route to
DifyAgentNode - If absent, route to the legacy
AgentNodeimplementation (preserving historical v2 tool-parameter semantics)
- If present, route to
- For all other cases, try to find the class registered under
node_version. - Fall back to the
"latest"sentinel if no exact match is found.
This means older saved graphs referencing version "1" continue to execute with AgentNode, historical graphs with version="2" without the discriminator use AgentNode, and new graphs with version="2" and agent_node_kind="dify_agent" use DifyAgentNode.
Node construction is delegated to DifyNodeFactory.create_node(), which reads the type, version, and node_data from the persisted node config, calls _resolve_node_class() (now passing node_data to support discriminator-based routing), then dispatches to a per-type kwargs factory. For BuiltinNodeTypes.AGENT, the factory checks issubclass(node_class, DifyAgentNode) to branch between V1 (plugin-strategy) and V2 (agent-backend microservice) initialization .
Discriminator check is implemented by is_dify_agent_node_data(), which inspects the three-part signature (type=agent, version=2, agent_node_kind=dify_agent). The discriminator is used at dispatch time, publish validation, DSL packaging, Composer candidate filtering, and Node Output Inspector routing to ensure only explicit Dify Agent nodes are treated as such.
The mutable view NODE_TYPE_CLASSES_MAPPING wraps the registry as a MutableMapping, enabling test overrides without mutating the global Node class state .
Frontend: Feature Flags & Node Palette#
The UI maintains a parallel distinction between the legacy agent block and the V2 block.
BlockEnum defines both as separate entries in web/app/components/workflow/types.ts:
BlockEnum.Agent = 'agent'BlockEnum.AgentV2 = 'agent-v2'
Both are included in WORKFLOW_COMMON_NODES, the master list of blocks available to the workflow engine. The selector palette shows both as separate entries, both titled "Agent".
Conditional exposure is controlled by useAvailableNodesMetaData():
- Reads the
NEXT_PUBLIC_ENABLE_AGENT_V2environment variable viaisAgentV2Enabled(). - Detects the workflow mode via
useIsChatMode(). - Computes
shouldUseAgentV2 = agentV2Enabled && (!isChatMode || isAgentV2InChatflowEnabled()). - Builds the full metadata list (
nodesMetaData) containing bothBlockEnum.AgentandBlockEnum.AgentV2. - Filters the node picker list (
availableNodesMetaData): ifshouldUseAgentV2, removesBlockEnum.Agent; otherwise removesBlockEnum.AgentV2. - Populates
nodesMetaDataMapfrom the unfiltered list, ensuring both Agent and Agent V2 validators remain accessible .
The result: exactly one variant appears in the node picker UI at any time, but both validators are retained in nodesMap. This allows workflows containing legacy Agent nodes to pass validation and publish successfully even after Agent V2 is enabled, while still guiding users toward the newer implementation for new additions.
Chat mode (Chatflow) handling: By default, Agent V2 is restricted to Workflow mode only. The separate NEXT_PUBLIC_ENABLE_AGENT_V2_IN_CHATFLOW flag (defaults to false) allows opt-in enablement of Agent V2 in Chatflow. When this flag is set to true, the isAgentV2InChatflowEnabled() helper returns true, and Agent V2 nodes become available in Chatflow mode.
Wire-type normalization: When a BlockEnum.AgentV2 node is placed, its defaultValue.type is normalized back to BlockEnum.Agent ('agent') before being written to the graph . The persisted type field remains 'agent' for both variants; the backend uses version ("1" vs "2") plus the agent_node_kind discriminator to select the correct class at runtime.
Key Source Files#
| File | Purpose |
|---|---|
api/core/workflow/node_factory.py | Registry bootstrapping, resolve_workflow_node_class, DifyNodeFactory |
api/core/workflow/nodes/agent/agent_node.py | AgentNode β V1 (plugin strategy), version() = "1" |
api/core/workflow/nodes/agent_v2/agent_node.py | DifyAgentNode β V2 (agent backend microservice), version() = "2" |
api/core/workflow/nodes/agent_v2/discriminator.py | is_dify_agent_node_data() β three-part discriminator check |
web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts | Feature-flag + chat-mode gating for node palette |
web/app/components/workflow/block-selector/constants.tsx | BLOCKS list β both Agent and AgentV2 entries |