Flow Nodes
The FengyuFlow canvas is a dataflow editor: every node is one tool invocation, every edge says "this node runs after that one and may read its output". This page explains what flows along the wires and how to debug one node in isolation. For the authoring side of the node declaration protocol, see Flow nodes in the plugin manifest reference.
Nodes and edges
- Start — the visual editor for the flow's run-time inputs. Each declared input renders as a typed chip row; referencing one on the canvas is
{{inputs.email}}. - Action nodes — one tool each. RPC JSON Schema supplies fields, types, requiredness, and defaults; an optional
flowNodesoverlay adds widgets, labels, placeholders, examples, and help. - Condition node (IF) — the built-in
flow_ifcontrol node. It compares two values and exposes two output ports, true / false. Every action node has exactly one output port; the condition node is the only one with two, because the port an edge leaves from is the branch semantics.
Edges are dependencies: a node runs once everything upstream has finished (ready steps run concurrently). Wiring is whole-node — you don't connect individual fields; a field may bind either to an upstream node's effective input or to its worker result.
Getting data in: references
Every input has three source states:
| State | What it does |
|---|---|
| ✏️ Manual | A literal value typed into the widget |
| 🔗 Reference | Pick from the variable tree: workflow inputs, upstream effective inputs, or upstream result fields, filtered by the expected type |
| ƒ Expression | Raw text with {{ }} placeholders — string templates for experts |
References serialize directly: {{node.<id>.input.<path>}} reads the value the upstream tool actually received after template resolution, {{node.<id>.result.<path>}} reads its worker result, and {{inputs.<name>}} reads a run-time input. Paths accept array indexes such as result.files[0]. The picker separates Input and Output, and unknown references are flagged before save.
Seeing what a node outputs
Anywhere outputs are listed you get three tiers, honestly labeled:
- Declared — the plugin's output schema (field names and types).
- Example — the declaration's example values.
- Last run — the node's real output from the most recent execution (truncated to 16 KB in the graph).
Hover a node's output port for the declared contract; open the node to browse the full output tree, copy a {{node.…}} reference path, or pin the last run.
Branch conditions and skips
Draw an edge from the IF node's true or false port and the compile step turns it into a runWhen condition on the target. At run time:
- The IF node evaluates (
contains,gt,is_empty, … — both operands may bind upstream references) and outputs{"branch":"true|false"}. - Steps whose branch did not fire are recorded skipped — gray badge on the node, no result, no tool call.
- A step whose dependencies were all skipped is skipped too (the dead branch cascades). A step with any live dependency still runs.
The AI generation node (LLM)
The built-in AI 生成 node runs one non-interactive model completion as an ordinary flow step, using whatever provider the AI settings currently have active:
- 提示词 (prompt) — the full instruction; bind upstream outputs with 🔗 references exactly like any other input (e.g. hand the Excel summary to the model for rewriting).
- 系统提示词 / 温度 (advanced) — an optional role ("你是邮件文案助手") and an optional sampling temperature 0–2; blank falls back to the global AI settings.
- 输出 Schema (advanced) — a JSON Schema object. When set, the model is instructed to answer with a matching JSON object, available under the 结构化数据 (data) output for field-by-field referencing (
{{node.x.result.data.sentiment}}). If the reply fails to parse or misses required fields, one repair retry feeds the exact error back into the prompt. The 原文 (text) output always carries the raw reply — structuring can fail, the answer never disappears. Pairdatafields with an IF node to branch on them.
The node builds a fresh model client per call, so it works inside parallel steps and even in flows executed from chat via run_current_flow without contending with the active conversation.
Debugging a single node
Run this node (in the node inspector's output section) executes only that node:
- Upstream result references resolve from each upstream node's pinned result or last run value; input references resolve from that node's configured effective arguments — no ancestors are re-executed.
- Workflow inputs bind from the current values in the flow settings.
- The result lands in the same places a full run fills: the node badge, the execution panel, run history, and the node's last-run preview.
If an upstream node has neither a pin nor a last run, the run tells you which one to run or pin first. Combined with rewind (re-run from step N after a full run) and pin, you can iterate on one node without the whole chain passing.
Checks before execution
Before any node runs, the host checks tool availability, reference paths, retry policies, and whole-value bindings whose declared types are incompatible (for example, an array bound to a string input). Errors identify the step and input path. Text interpolation remains a string; unknown or overlapping types still require runtime validation. Fixed results are also checked for failure envelopes and against declared output schemas before the first node runs, so an invalid later pin cannot cause earlier nodes to execute. These checks do not prove that external services are available or replace runtime input and output validation.
Pinned results
Pinning freezes a node's last run as its authored result: the engine serves the pinned value verbatim, never calling the tool — useful to iterate downstream against a known payload. Pinned nodes carry a 📌 marker; publishing a flow with pins is allowed but the pins stay in effect until removed.
Failure retries
The node inspector exposes Failure retry only when the tool is retry-safe. Choose 1–5 total attempts and an initial delay; the delay doubles after each failure and is capped at 30 seconds. Approval happens once before the attempt sequence, and run history records one terminal step result. While retrying, the canvas and execution panel show the upcoming attempt, delay, and last error; the same attempt timeline remains visible when reopening a persisted run.
Read-only tools are retry-safe automatically. A write or external plugin tool must explicitly declare idempotent: true, meaning repeating the identical invocation cannot duplicate a write, message, charge, or other side effect. Tools without that guarantee cannot be retried; the backend also rejects an unsafe policy supplied outside the UI before the first call.
Starting a published flow externally
The run dialog can turn a published flow into a durable loopback webhook. The values currently in the dialog become defaults; each incoming JSON object's fields override them. The one-time secret and optional event ID protocol are documented under Workflow webhooks. Flows with picker-file or auto-shared-directory inputs cannot create webhooks because those grants are session-scoped rather than durable.
Draft and restart recovery
Unsaved canvas edits are written to a workflow-scoped local draft after a short debounce. Reopening the route offers to restore it; saving or intentionally discarding clears it. A backend restart during a run records a Recovery required checkpoint instead of pretending the run failed. Resume always returns to plan review and reuses stable per-step invocation IDs so idempotent workers can deduplicate an uncertain call. Runs whose remaining steps contain session-scoped file grants cannot resume after restart; start a new run and select those files again.