update_workflow
Batch XML mutations on a workflow with optional progressive disclosure.
XML tags: after= supports "node:handle" syntax for multi-output nodes, e.g. after="conditional:true" or after="iteration:loop" handle is REQUIRED for multi-output nodes (iteration: loop|done, switch: case values, conditional: true|false). Output handles are returned by include_operations and shown in get_workflow XML. For iteration nodes: add a loop-back edge from the last body node TO the iteration node to mark which body node output to aggregate into collected_results. Example: — TOOL-PROVIDER wiring: instead of dataflow, the source node's operations become callable agent tools (e.g. a linear node exposes linear__create_issue). Then set the operation allowlist: (operation names from include_operations; invalid names are rejected with the valid list). The agent also gets an auto-included {provider}__lookup_options tool for ID fields. Provider-wired nodes do NOT execute in the flow and cannot also feed dataflow consumers, and cannot have a trigger operation selected (either-or — use a separate node per role); they still need credentials (set_credentials) for the agent to call their tools. Use this whenever an AI agent should ACT on a service (create/update/search) rather than a fixed pipeline step. Provider edges appear as type="tools" in get_workflow XML. Rule of thumb: deterministic steps with known inputs → dataflow pipeline; open-ended instructions where the agent picks actions and arguments → provider wiring. When the request is UNDERSPECIFIED (a goal and the services involved but not the exact steps, fields, or branching), PREFER an agent with the relevant integration node(s) wired as tool providers over guessing a rigid pipeline — the agent resolves the specifics at runtime and degrades far more gracefully than a hardcoded flow built on assumptions. Keep its input minimal too: wire any trigger STRAIGHT into the agent rather than a trigger->node->...->agent chain of fetch/transform nodes — the agent fetches itself. The AGENT node's built-in chat (streaming, history) shows in the Interface tab by default — set show_in_interface="false" only when the user explicitly asks to hide it (the chat is also the Test Run surface), and never build a custom chat interface component. Prefer the agent node for open-ended "act on my behalf" work over a one-shot LLM integration node. The agent runs a plain LLM or a full agentic HARNESS — Claude Code/Codex/OpenCode/OpenClaw/Hermes, each a CLI agent with its own built-in tools in a sandbox — set via the agent node model; these are agent models, NOT standalone node types (there is no hermes/codex node). GitHub providers can also MOUNT repos into the agent sandbox: — entries are "owner/repo" strings or {"repo": "owner/repo", "branch": "dev"} objects; each is cloned with push access at run start; the agent edits/pushes via execute_bash and opens PRs with github__create_pull_request. RARELY, when an agent must call an API that has NO NoClick node from its shell, request sandbox env vars by declaring their NAMES: (names only — the user provides values, which become a credential you can never read). Prefer a provider or HTTP Request node; only use env vars when no node exists for the API. — TRIGGER wiring: a trigger wired directly into an agent delivers its fired event as part of the agent's user turn automatically. Do NOT template trigger references into the agent message — it holds standing instructions; multiple triggers can feed one agent and only the fired one delivers. Channel triggers (Telegram/Slack message, alarms) also auto-supply their chat/thread id as the conversation key (per-chat history). For replies into the channel, wire the same service as a tools provider and allowlist its send operations. Trigger choice: the trigger-* nodes each create a NEW NoClick-hosted entry point (trigger-webhook a URL, trigger-email a name@noclick.app inbox, interface-form a public form, trigger-cron a schedule, trigger-run a manual button) and do NOT read the user's existing accounts. For something they already own ("my inbox/Slack/calendar/sheet"), use that integration's OWN trigger operation instead (its x-is-trigger op, e.g. automation-gmail poll_for_new_emails); reserve trigger-* for a genuinely new endpoint. large value {"key":"val"} update_settings fields: retryOnFail (true|false), maxTries (2-5), waitBetweenTries (0-5000 ms), onError (stopWorkflow|continueRegularOutput|continueErrorOutput), alwaysOutputData (true|false), executeOnce (true|false), notes (free text). All fields are optional — only provided fields are updated. unified diff patch_config format (simplified unified diff): @@ anchor_line (locates position in existing code), -old_line (remove), +new_line (add), (space)context_line (unchanged). Example: @@ function App() / - return old / + return new markdown content markdown content add_sticky_note positioning modes: cover (after+before spans bounding box between two nodes), near (near+direction places adjacent to node group). Anchor params are persisted so sticky notes reposition automatically when nodes move via autolayout. color=0-8 (default 8=black).
Reserved attrs per tag: add_node(type, name, label, after, operation), add_edge(from, to, handle, type), remove_edge(from, to, handle), remove_node(id), update_config(id, field), set_credentials(id), disable_node(id), enable_node(id), mock_node(id, output), unmock_node(id), update_settings(id, retryOnFail, maxTries, waitBetweenTries, onError, alwaysOutputData, executeOnce, notes), patch_config(id, field), add_sticky_note(name, after, before, near, direction, color, width, height). All other attrs are config.
name= on add_node is a local alias usable in from/to/after/id within the same batch.
Dynamic option suffixes in update_config: key__fuzzy="query" - auto-resolve dynamic field by fuzzy match (sets value if 1 match) key__search="query" - preview matching options without setting value key__search_limit="N" - limit search results (default 10, max 50) operation__fuzzy="query" - fuzzy match operation by name or description (auto-sets if 1 match)
Flags:
include_operations: returns available operations for each added node type (+ output_handles for multi-output nodes, + per-operation output_schema from past runs showing reference paths)
include_configs: returns config schemas for nodes where operation was set (includes credentials)
include_dynamic_options: auto-load first-level dynamic field options for touched nodes
Processing order: add_node → add_edge → add_sticky_note → update_config → patch_config → set_credentials → disable/enable → mock/unmock → update_settings → resolve dynamic options → remove_edge → remove_node
IMPORTANT — Sticky note best practice: After building or modifying a workflow, ALWAYS add covering sticky notes to label each major section. Use cover mode (after+before) with a short markdown title only (e.g. '## Data Processing'). For complex workflows, add additional nearby sticky notes with descriptive content explaining the section. This makes workflows self-documenting and easy to understand at a glance.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| updates_xml | Yes | ||
| workflow_id | Yes | ||
| idempotency_key | No | ||
| include_configs | No | ||
| include_operations | No | ||
| dynamic_options_limit | No | ||
| include_dynamic_options | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||