Workflow Agent Node Configuration#
A Workflow Agent Node (type agent, version 2) embeds an Agent V2 execution unit inside a Dify workflow graph. Its configuration is split across two persisted objects:
- Agent Soul (
AgentSoulConfig) β the agent's identity: model, prompt, tools, knowledge, environment, and sandbox. Stored as a JSON snapshot inAgentConfigSnapshot.config_snapshot. - Node Job (
WorkflowNodeJobConfig) β workflow-specific wiring: declared outputs, previous-node output refs, human contacts, and task prompt. Stored inWorkflowAgentNodeBinding.node_job_config.
This separation means the same Agent Soul can be reused across multiple workflow nodes (Roster agents), while each node carries its own workflow wiring independently.
Key source files:
| File | Role |
|---|---|
api/core/workflow/nodes/agent_v2/validators.py | WorkflowAgentNodeValidator β draft and publish validation |
api/core/workflow/nodes/agent_v2/dify_tools_builder.py | WorkflowAgentDifyToolsBuilder β tool layer construction |
api/core/workflow/nodes/agent_v2/runtime_request_builder.py | WorkflowAgentRuntimeRequestBuilder β assembles CreateRunRequest at runtime |
api/models/agent_config_entities.py | All config DTOs (AgentSoulConfig, WorkflowNodeJobConfig, AgentSoulDifyToolConfig, etc.) |
Validation: Draft vs. Publish#
WorkflowAgentNodeValidator has two entry points with different strictness levels:
validate_draft_workflowβ iterates all Agent v2 nodes in the graph, validates the node schema viaDifyAgentNodeData.model_validate, and checks any existing binding. A missing binding is silently skipped (the node is unconfigured but not yet in error).validate_published_workflowβ same checks, but a missing binding is a hard error. It also validates graph topology (upstream relationships for previous-node output refs).
Agent v2 nodes are identified by three conditions:
node_data.type == BuiltinNodeTypes.AGENTnode_data.version == "2"node_data.agent_node_kind == "dify_agent"
The three-part check is encapsulated in is_dify_agent_node_data. Historical Agent nodes with type=agent, version=2 but without the agent_node_kind marker are routed back to the legacy Agent implementation for backward compatibility.
Binding Validation#
validate_binding accepts an optional require_agent_model: bool = True parameter that controls whether the agent soul model configuration is required (True for publish validation, False for draft validation). It runs these checks in order:
- Agent existence β the bound
Agentrow must exist, be in the correct scope (ROSTER vs. inline), and not be archived. - Snapshot existence β the active
AgentConfigSnapshotmust exist for the resolved snapshot ID. - Model presence β
AgentSoulConfig.modelmust not beNonewhen validating published workflows (controlled by therequire_agent_modelparameter). Draft workflow validation allows model-less inline Agent nodes to support incremental configuration. RaisesWorkflowAgentNodeValidationErrorif missing when required. - Environment variables β agent-level and CLI tool env names must be unique and all secret refs must have an authorized permission status.
- Tools β enabled Dify tool names must be unique; enabled CLI tools must be pre-authorized, have no denied permissions, and acknowledge dangerous commands.
- Knowledge datasets β all referenced dataset IDs must exist within the tenant.
- Prompt mentions β when publishing, all Skill and File mentions in the agent prompt must be resolvable. Skill mentions are satisfied by Skills embedded directly in the Agent node's
config_skillsor by workspace Skills bound to the selected agent snapshot viaAgentSkillBindingSnapshot. Workspace Skills are resolved using the same runtime Skill resolution logic as execution (SkillManagementService.list_runtime_agent_skills), so publish validation matches what will happen at runtime. If all Skill references are already satisfied by embedded configuration alone, workspace Skills are not loaded (optimization). File mentions must match aconfig_filesentry. Missing references raise a publish-blocking error.
Node Job Validation#
validate_node_job checks:
- Locked soul fields β the
node_job.metadatamust not contain any key from_LOCKED_AGENT_SOUL_KEYS(e.g.,agent_soul,tools,model). This prevents the node job from overriding soul-owned configuration. - Duplicate output names β declared outputs must have unique names.
- Reserved output names β custom outputs cannot use reserved names (
text,switch,_session). These are defined inRESERVED_DECLARED_OUTPUT_NAMES. - File refs β benchmark file refs on output checks must be resolvable
UploadFilerows within the same tenant. - Previous-node output refs β each ref must resolve to a valid
[node_id, output_name]selector, and at publish time, the source node must exist upstream of the agent node in graph topology. - Human contact refs β each human contact must have a valid
contact_idand an allowedchannel.
Tool Node Agentic Mode (Publish-time)#
At publish time, the validator also checks all Tool nodes in the graph. If a tool node has an agentic_mode config, it must be complete, authorized, and either in a manual state or have an inferred parameter draft.
Runtime: Tool Layer Construction#
When a run starts, WorkflowAgentDifyToolsBuilder.build_layers translates the AgentSoulToolsConfig into two Agent backend layer configs:
DifyPluginToolsLayerConfigβ forPLUGINprovider types (routed to Plugin Daemon)DifyCoreToolsLayerConfigβ forBUILT_IN,API,WORKFLOW, andMCPproviders (routed to internal API atPOST /inner/api/agent/tools/invoke)
The builder follows these steps per tool:
-
Provider expansion β if
tool_nameisNone(provider-level entry = "all tools"),_expand_provider_entriesqueries the provider for its declared tool names (viaToolManager,WorkflowToolProviderController, orMCPToolManageService) and emits one entry per tool. Already-explicit tool names are skipped to avoid duplication. -
MCP normalization β MCP provider IDs are resolved to their runtime server identifiers via
_resolve_mcp_provider_id. -
Tool runtime fetch β
_fetch_tool_runtimecallsToolManager.get_agent_tool_runtime. Errors map to stableWorkflowAgentDifyToolsBuildErrorcodes:agent_tool_declaration_not_foundβ provider or tool not foundagent_tool_credential_invalidβ credential validation failedagent_tool_config_invalidβ otherValueErrorduring runtime construction
-
Required parameter check β
_runtime_parameterschecks that every non-LLM, required parameter with no default has a value inruntime_parameters. Missing ones raiseagent_tool_runtime_parameter_missing.Note: This is the source of the
"tool parameter {name} not found in tool config"error seen in issue #15468. The lower-levelinit_frontend_parameterinparameters.pyraises aValueErrorwhen a required parameter (e.g.,instruction) has no value and no default. This propagates up through the tool initialization chain. -
Plugin JSON schema augmentation β
_plugin_parameters_json_schemapatchesFILEandFILES-typed LLM parameters into the schema using aanyOfshape that accepts URL strings or{transfer_method, url/reference}objects. -
Credential normalization β only scalar values (
str,int,float,bool,None) are forwarded to the Plugin Daemon; non-scalar credential values raiseagent_tool_credential_shape_invalid.
Runtime: Request Assembly#
WorkflowAgentRuntimeRequestBuilder.build assembles the final CreateRunRequest sent to the Agent backend:
- Validates the soul model config is present (raises
agent_model_not_configuredif not). - Calls
build_layersto produce tool layer configs; mapsWorkflowAgentDifyToolsBuildErrortoWorkflowAgentRuntimeRequestBuildError. - Builds prompts:
- Soul prompt β from
agent_soul.prompt.system_promptwith prompt-mention resolution. Skill mentions are resolved from embeddedconfig_skillsentries and workspace Skills bound to the agent snapshot (SkillManagementService.list_runtime_agent_skills). File mentions are resolved fromconfig_files. - Workflow task prompt β from
node_job.workflow_promptwith mention resolution. Staleprevious_node_output_refsnot referenced in the current prompt are discarded. - Workflow context prompt β assembles
sys.query,sys.files, and resolved previous-node output values.
- Soul prompt β from
- Builds structured output schema from
node_job.declared_outputsβ the structured-output layer is only used when custom outputs are configured. Without custom outputs, the backend uses a plain string contract and the Agent backend output layer is omitted entirely. For structured runs, a system-ownedtextoutput is prepended to custom declarations at runtime (not persisted). - Builds knowledge, shell, ask-human, and execution-context layer configs from agent soul fields.
- Assigns an idempotency key scoped to
(workflow_run_id, node_execution_id, attempt)so retries get distinct keys.
Output Normalization#
The output contract differs for plain and structured runs:
- Plain runs (no custom outputs) β the backend produces a free-form string, which is mapped directly to the system
textoutput. The structured-output layer is omitted. - Structured runs (custom outputs configured) β the system
textoutput is prepended to custom declarations. Thetextfield is optional and free-form; only custom fields undergo type checking, retry/default handling, and file normalization. - File normalization β only structured runs with declared
FILEorARRAY[FILE]fields undergo file normalization. TheWorkflowAgentOutputAdapterconverts canonical file mappings intoFileSegmentvalues for those declared fields.
The effective output projection is implemented by effective_declared_outputs in api/models/agent_config_entities.py. This function prepends the system text output from SYSTEM_DECLARED_OUTPUTS to the custom declarations. The system output is:
- Name:
text - Type:
STRING - Required:
false - Description: "Free-form text answer."
The constant SYSTEM_DECLARED_OUTPUTS replaces the former DEFAULT_DECLARED_OUTPUTS, which included preset files and json outputs. Those preset outputs have been removed, and users may now define custom fields named files or json without conflict.
Stale Tool Parameters#
When a tool is removed from the Agent Soul config, its saved runtime_parameters entries in AgentSoulDifyToolConfig are simply absent from the enabled tool list. build_layers only processes tools where tool.enabled == True, so orphaned parameter entries for removed tools are implicitly dropped β they are never forwarded to the Agent backend.
However, if a tool is re-added with changed parameter definitions, any previously stored runtime_parameters that no longer match declared parameters will surface as agent_tool_runtime_parameter_missing errors at runtime if a now-required parameter is absent. The fix is to update the tool's stored runtime_parameters in the Agent Soul config via the Composer UI.