Skip to main content
This is the field guide to the nodes you drag onto the Agent Flows canvas. Each node does one job; you wire them together into a graph that compiles to a real LangGraph agent. Everything below is generated from the live node registry, so the names, settings, and ports match exactly what you see in the canvas.
New to Agent Flows? Read Agent Flows for the canvas basics (creating, publishing, enabling a flow as a chat sub-agent). For end-to-end example flows you can copy, see the Agent Flows Cookbook. This page is the reference for the individual nodes those recipes use.

How data flows between nodes

Two different things connect your nodes, and it helps to keep them separate in your head:
  • Edges = order. An edge (the line you draw from one node to the next) decides when a node runs. start → A → B → end means run A, then B. Reading another node’s value counts toward that order too (see the note below), but the edge is what you should rely on.
  • Bindings = data. A binding decides what value feeds a node’s input port. You bind an input to one of these sources:
You rarely type these paths by hand. When you connect two nodes, the canvas auto-wires compatible ports for you. To send one specific value, drag from an output port to an input port. Prompt fields also accept inline templates — {{ params.message }}, {{ inputs.context }}, {{ nodes.<id>.outputs.text }} — so you can weave values straight into the text.
A node with two incoming edges waits for both before it runs. That’s how you fan two parallel branches back into one node (e.g. a Knowledge Graph branch and a Web research branch both feeding one LLM generate). You only need the explicit Join node when you fanned out with Map.Reading another node’s output counts as a dependency too — whether you bound it ($.nodes.<id>.outputs.<port>) or spliced it into a prompt ({{ nodes.<id>.outputs.<port> }}) — which pushes the reading step later in the run. Treat that as a safety net, not a guarantee: when the reader and the node it reads are both waiting on other branches, they can still end up running together, and the reader gets nothing. Draw the edge as well — it’s the only reliable way to express the order, and the canvas won’t flag the missing one for you when you validate or publish.If a step fails, the steps that read its output don’t run on empty input — the run stops with an error naming the source that failed, instead of carrying on and producing a hollow answer that still reports success.
Nodes fall into five categories in the palette: Control, Data, LLM, Agents, and Human. Here’s every one.

Control nodes

The plumbing: where a flow starts and ends, and how it branches, repeats, and fans out. Control nodes don’t call a model — they’re fast and deterministic. Condition (control.condition) — set Value path to the value you’re testing (e.g. $.nodes.kg.outputs.row_count), an Operator (eq, ne, gt, gte, lt, lte, in, not_in, contains, truthy, falsy), and a Comparison value when the operator needs one. The edges leaving a Condition decide which branch runs on true vs false. Map (control.map) — set Body node to the node that runs once per item, and Join node to the control.join that collects the results. Inside the body, read the current item with $.nodes.<map_id>.outputs.item (and .index). The list you bind to items is fanned out in parallel. Join (control.join) — the other half of a Map (the Map’s Join node setting is what points at it). It collects every branch once they’ve all settled, and two settings tune what it counts as a branch that produced something: Body output port (default text) names the port on the map body that carries the branch’s content, so the Join knows where to look when it judges whether a branch came back empty; Minimum body length (default 0, off) puts a floor on that port’s length. results is one entry per branch in item order and count is how many branches there were. results_by_name keys the usable results by the map item they came from and accumulates across loop passes, so a second, narrower pass that re-authors three sections doesn’t throw away the twelve an earlier pass got right. Only branches that came back with usable content are recorded there — so a degraded re-run never evicts a good earlier result, and results_by_name and ok can legitimately disagree. Failing a branch and dropping its content are two different decisions: a branch that is failed for how it finished keeps its content in results_by_name — for an item nothing has recorded yet — because evicting a complete section replaces a defect you can see with a hole you cannot. Where an earlier pass already recorded that item, the condemned re-author is thrown away instead and the earlier result stands: the map holds the best known good result per item, so a condemned second attempt must never replace work an earlier pass got right. A branch the Join cannot key (the Map’s item list doesn’t name it, or two items share a name) is counted as failed rather than quietly left out: a result that never reaches results_by_name is a section missing from whatever you assemble from it. Route on ok, not on failed_indexes. failed_indexes is built from the branch count, so a Map that fanned out over an empty list produces an empty failed_indexes that reads exactly like “every branch succeeded” — and a Condition testing it sends the run on to assemble a document with no sections in it. ok is true only when at least one branch ran, none of them failed, and nothing is still held back on condemned_items or discarded_items — which is the check you actually want. Those last two terms are folded in because count and failed_indexes are rebuilt from the branch count on every execution and so describe the current pass only, while condemned_items and discarded_items survive it: a later, narrower pass that re-authors three sections and comes back clean must not flip ok true while an earlier section is still unapproved or missing. failure_reasons maps a branch index to why that branch counts as failed, for a message or a log. condemned_items and discarded_items name which planned sections went wrong, where failed_indexes only gives you positions. condemned_items names the map items whose own branch failed for routing — a skipped sign-off, a dead approval gate, meta-commentary — and whose text that branch did record, so the section can still be assembled but is unapproved. discarded_items names planned items this Join failed and recorded nothing for, so there is no text at all: wording that blames a discarded section’s approval gate is false, it was degraded and dropped. Both survive the pass that produced them, and both are released the same way — a later pass that re-produces the item with no failure signal drops it from condemned_items, and any pass that records something under the name drops it from discarded_items. Feed both back into whatever chooses the sections to re-author, and bind both anywhere you bind one, each with a default of [], because the ports are absent until the Join first holds something back — which is what the RFP Response Pipeline template does.
What counts as a failed branch. A branch that crashed, one whose step reported degraded work (including an agent that never called finish — see below), one whose body port came back empty, one whose agent had a block tool fail and never got it working, one whose body falls under Minimum body length, and one whose body carries meta-commentary — the agent narrating its own tooling inside the deliverable (a grounding disclaimer, a raw traceback, a leftover TODO marker). This is simply how a Join behaves: there is no per-flow strict switch and no deployment-wide setting to turn it on, so ok means every branch came back with real content in it, not just that nothing crashed.An agent that skipped its sign-off keeps its work. An orchestrator is told it must call finish when it’s done. When the loop ends without that call the branch is reported as failed and goes back through your revise lane — that really is off-policy and you should see it — but what the agent wrote is still recorded in results_by_name, because a missing tool call says nothing about the section. It is judged on the same signals as any other branch: an empty body, a body under Minimum body length, or a block tool that died still drops it. This is only about the sign-off; an agent that reports partial is telling you the work itself fell short, and that branch is dropped as before.A dead approval gate holds a section back rather than deleting it. A branch whose only unresolved failing block is a human.review gate is routed as failed, but it keeps its content in results_by_name: the gate authors no prose, so its death means the section is unapproved, not untrustworthy, and — when nothing else in the body is at fault — the reason recorded for that branch in failure_reasons reads approval gate did not complete (N block failure(s)); section unapproved. The carve-out is deliberately narrow — it applies only when the step was degraded rather than raised, only when every unresolved failing block is that gate, and only when the branch’s own status is clean. A branch reporting partial, or one that also skipped finish, is content-fatal and dropped, as a partial branch always was, and a failed knowledge or content block still evicts.A tool the agent retried successfully doesn’t count against it. A transient error — a graph database blip mid-run — makes the agent’s tool call come back as a failure, and the agent’s documented next move is to try again. When the retry works, the branch is a branch that recovered, not a degraded one, so it isn’t held against the section or the run. The blip is still recorded and still visible on the step; it just doesn’t fail the work that came out correct.Text your flow quoted isn’t your agent’s own words. A [TODO: ...] or [placeholder: ...] marker inside quotation marks, a blockquote, or a code fence reads as material quoted from the customer’s own document — quoting an RFP’s boilerplate back at it is what a compliance response does — so it doesn’t fail the branch. The agent narrating its own tooling (a grounding note, an “as an AI” hedge, a traceback) is never excused this way, however it’s wrapped. Note the carve-out only covers marked quotation: a marker spliced into the agent’s own sentence still counts as the agent’s own.A body port that isn’t there is judged on intent. If you set Body output port, you’re declaring where the content lives, so a branch that comes back without that port is a failed branch. If you leave it at the default text, a body node that produces no text port at all is not held against the branch — that body simply doesn’t use it, and the branch is judged on its other signals (step status, block failures). This is what lets a Map whose body is, say, a Data block work normally; publish-time validation rejects the two definitions where this can only be a mistake — a body that cannot produce the port you named, and a Minimum body length on a port the body never produces (the floor could never fire).Minimum body length is a stub floor, not a quality bar. In the runs this was measured against, good sections and degraded ones overlapped completely in length (2,844–18,339 characters against 400–15,782), so no threshold cleanly separates them. Set it low enough to catch an obvious stub, and expect a high value to reject good work.
Loop (control.loop) — set Max iterations (1–1000). Every cycle must pass through the Loop node; once the count hits the max, exhausted flips to true so you can route out of the loop. End (control.end) — End is where the flow’s answer comes from, so always bind its result to whatever produced the final value. Two optional settings decide what happens when nothing did: Require a result (default off) fails the run when every bound input resolves to nothing, and Failure message is what the run reports when it does. Turn Require a result on for any flow whose whole point is a deliverable. A route that skips the producer — an exhausted revise loop, a rejected approval — reaches the exit with its binding unresolved, and because nothing crashed the run would otherwise settle Completed with no document attached. “We gave up after three revision cycles” and “here is your document” should not look the same on the runs list. Leave it off for a flow whose job is a side effect (post a message, write a row) and for anything that can legitimately end on 0, false or an empty list — the check is “the binding resolved to nothing”, not “the value is falsy”, so those still pass either way. Start and Merge need no settings in the simple case — just wiring. Merge has two optional ones for a merge a loop can revisit; see below.
A Merge a loop can re-enter needs a selector. By default a Merge forwards the first bound input that isn’t empty, which is correct when the merge is reached once. Node outputs accumulate, so on a second pass through a Condition the branch you took the first time is still sitting there — and it keeps winning. Set Selector path to the Condition’s own result (e.g. $.nodes.route.outputs.result) and Selector map to {"true": "<binding>", "false": "<binding>"}, and the branch that ran this pass is the one that’s forwarded. If the branch it names produced nothing, the Merge forwards nothing rather than falling back to the abandoned branch: an empty value is visible downstream, a stale one is not.

Data nodes

These work with your content: query the knowledge graph, read the conversation a run came from, index uploaded files, and render documents. Knowledge Graph Query (knowledge_graph) — the workhorse for answering from your own data. Key settings:
  • Output mode — data returns rows only; report additionally synthesizes a cited narrative on the text output (with evidence). Pick report when you want a written answer, not just a table.
  • Query — the question. Leave it templated or bind the query input to Chat message so the user’s question drives it.
  • On clarify — what to do when the question is too vague to scope: fail (stop), interrupt (pause for a human), or skip (continue with empty rows).
  • Fail on empty, Include graph context, Custom instructions, and report-tuning options (Output instructions, preview/token caps) are available for finer control.
Large result sets (over ~1,000 rows) are automatically exported to a CSV file artifact and surfaced on the rows_file output, so the run stays lightweight.
Knowledge Graph data (kg.data) — the same scope-and-query as Knowledge Graph Query, minus the report writer. Set the Query and optional Custom instructions, and choose an On clarify policy (fail / interrupt / skip) exactly as above; Fail on empty, Include graph context, and Document search K (how many parallel document searches run during scope — default 3, 0 to disable) tune the retrieval. It exposes the scope and retrieval outputs directly — resolved_entities, intent, validated_cypher, graph_context, scope_recommendation, clarification_question, plus rows/row_count/retrieval_quality — with the same automatic CSV export on rows_file for large results. Reach for it when a downstream node consumes the graph data itself. When you also want a written narrative, you have two routes: keep the data step and wire a Knowledge Graph Display node after it (below), or collapse both into a single Knowledge Graph Query in report mode. Knowledge Graph Display (kg.display) — the report half of the pair. Wire kg.data → kg.display and it runs the deep agent’s report writer over the rows kg.data already retrieved — no second query — to synthesize a main-agent-quality, cited narrative on text (with an evidence index). The canvas auto-wires the ports it reads from kg.data (rows, resolved_entities, intent, validated_cypher, graph_context, scope_recommendation, clarification_question); rows is the only required input. Configure the Query it answers (falls back to the upstream intent when left blank), optional Output instructions (formatting and guidance for the report writer — the report-only Display node has no scope phase, so it takes no separate custom instructions), the report’s row budget and token budget (report_data_preview_limit, default 300; report_max_data_tokens, default 50,000), and an optional model override for the writer. To surface the answer, finish the pattern start → kg.data → kg.display → end and bind end.result ← kg.display.text — the evidence index rides along with the text, so the grounded [evidence:…] citations render as clickable source chips in the run output panel (document chips link out to the source; graph-entity chips show the entity by name).
One node or two? Use Knowledge Graph Query (report mode) for a straight question → cited answer in a single block. Split it into kg.data → kg.display only when you need the rows between the two steps — to branch on row_count, inspect or transform the data, or feed it somewhere else as well as report on it.
Time limits. The knowledge-graph nodes are capped on wall-clock time, and a node that runs past its cap fails with a timeout instead of returning partial results: Knowledge Graph Query and Knowledge Graph data get 600s, Knowledge Graph Display 300s. The cap applies to every run of the flow, including versions published before it existed. You can’t see or change these from the Inspect panel — its Budget editor only appears on the agent nodes — but you can raise one through Edit as JSON by setting budget.max_wall_seconds on the node.
Index uploads (data.index_files) — bind message file ids to an uploaded file slot ($.files.<slot>). It indexes the files and also emits short text previews on documents that downstream LLM nodes (like llm.extract) can read directly. Read conversation (data.channel_history) — nothing to bind: it reads the chat the run was launched from. Set Messages to read (how many of the most recent messages to take — default 20, up to 200; they come back oldest-first), Whose messages to include (all, user for only what the person typed, or assistant for only the agent’s replies), a Per-message character cap (default 40,000; 0 disables truncation), and Fail when the conversation is empty (on by default — leave it on when a downstream node depends on the transcript, since an empty transcript renders as an empty prompt and yields a hollow document that still reports success). transcript is the plain-text User: … / Assistant: … version to feed an LLM node; messages is the same turns structured as {message_id, role, text, truncated, created}, with message_count alongside.
Read conversation only works on a run launched from a chat. A run started from the canvas Run tab (or the REST API) has no conversation to read, so the node fails with a “requires a channel-scoped run” error. It also only ever reads a conversation the person who started the run can open — one they own, or one shared with them directly or through a folder. Anything else fails with an authorization error rather than being read — distinct from the empty-conversation failure above, so you can tell the two apart in the run’s step log.
Generate DOCX (data.generate_docx) — bind sections (a list of structured {heading, content} objects) for a multi-section document, or bind text for a single-section body (e.g. a Knowledge Graph report). Set the Document title, optionally Include table of contents, and choose a Theme from the eight built-in ones. To brand the output, pick a Document Template by name (one of your registered Document Templates); the document then inherits that template’s styles, headers/footers, and logo. Generate PPTX (data.generate_pptx) — the slide equivalent of Generate DOCX: bind sections (one slide each) or text, set the Title, and optionally pick a PPTX template by name for branded slide masters. (A Theme is accepted for parity with DOCX but does not currently affect slide styling — use a template to brand slides.) Read Excel template (data.inspect_xlsx) and Fill Excel template (data.generate_xlsx) — a pair for filling a spreadsheet you already have. Unlike the Generate nodes, which build a document from scratch, these edit an existing workbook in place: only the cells you fill change, and the file keeps its images, styles, conditional formatting and formulas. Point both at the same Template name (an Excel Document Template) and, optionally, list the Sheets to limit them to. Read Excel template produces a plain-English description of the form on text — the labels it prints, the columns of its line-item table, and, for any column the workbook looks up (a commodity code, say), the exact set of values that will resolve. Bind that to an LLM node’s context and the model drafts using the form’s own wording instead of guessing. Fill Excel template takes that draft back:
  • fields — single-value fields, as {label, value} entries or a {label: value} object. Matched against the labels the form prints, so E-Mail Address and email address both land.
  • blocks — multi-line boxes, as {label, lines}. For a caption printed inside its own box (a TO: address block), the lines go in the rows beneath it and the caption is left alone.
  • rows — line items, each keyed by the table’s own column headers.
  • cells — exact overrides as {sheet: {A1: value}}, when you already know where a value goes.
Dates land as dates, lookup keys keep their leading zeros, and columns the workbook computes for itself are never overwritten — their cached results are refreshed instead, so the file reads correctly everywhere and still recalculates in Excel.
Both blocks stop the run rather than hand back a workbook that only looks filled. Fail if nothing was filled is checked per region — filling the header is not evidence the line items filled — and a value outside the workbook’s allowed set is an error, not a silent skip, because a blank there leaves every computed column empty.
Generate file (data.generate_file) — a single node with a format chooser: Word (.docx), PowerPoint (.pptx), PDF, or HTML. It reuses the same generators as the dedicated DOCX/PPTX nodes. The Document Template selector appears only for the docx and pptx formats. For pdf/html, bind content to the format-native source (LaTeX for PDF, HTML for HTML); for docx/pptx, bind sections (or text) as you would for the dedicated nodes.
Markdown that shows up in generated content — #/##/### headings, **bold**/*italic*, -/1. lists, and pipe tables (| Col | Col | with a |---|---| separator row) — is rendered as real Word/PPTX styling rather than left as literal markers in the document. A pipe table becomes a proper Word table with a header row and borders; in PPTX, where slides have no table layout, each row is rendered as a line.
A document node won’t hand back an empty deliverable. Generate DOCX, Generate PPTX, and Generate file each have Fail when there is nothing to write, on by default. With it on, a node given no sections and no text — or a list of sections with nothing in any of them — stops the run with an error saying it was given nothing to write … so the result would be a title-only document reporting success, instead of writing a title-only file and reporting success anyway. Untick it only when a shell with no body is genuinely the output you want.The check knows what each format can actually render. A section carrying only a table counts as content for .docx, where the writer renders it as a real Word table, but not for .pptx, where a slide has no table to fill and the section would come out blank. (A markdown table written inside a section’s content does render on a slide, so it counts either way.) For the PDF and HTML formats of Generate file — which take one source string rather than sections — the same setting refuses an empty body.
The hidden phases of Knowledge Graph Query — kg.scope, kg.retrieve, kg.report — are covered under Advanced / hidden nodes below. You don’t need them for normal flows.

LLM nodes

Single-call language-model steps. Each makes one model call from a prompt (plus optional context input) — predictable cost, no looping. All three accept {{ params.* }}, {{ inputs.* }}, and {{ nodes.<id>.outputs.* }} templates in their prompts. LLM generate (llm.generate) — set the Prompt (required) and optionally a System prompt. Bind context to whatever you want appended to the prompt (e.g. a Knowledge Graph report). Output lands on text. LLM extract (llm.extract) — set the Prompt plus an Output schema: a map of {name: {type, description, required}} where type is string | number | integer | boolean | array | object. The model is forced to return exactly that shape on the data output. Example schema:
LLM classify (llm.classify) — set the Prompt and a list of Categories (at least two, unique). The output category is guaranteed to be one of your labels, with a one-line reasoning. Wire category into a Condition to route.
All three take an optional Model (defaults to the global reasoning model), an Output format hint, and Include graph schema (adds a compact knowledge-graph schema to the prompt when the step reasons over graph data).

Agent nodes

These run a bounded, model-driven loop — the model decides its own steps using tools until it finishes or hits its budget. Because they loop, every agent node has a budget (max model calls, tool calls, tokens, wall-seconds) so it can never run unchecked. Unset budget fields fall back to sensible defaults. Knowledge research agent (agent.deep_research) — set the Research goal (templatable; use {{ params.message }} to use the chat message). By default it uses the proven internal-knowledge tool set (knowledge graph + Cypher); you can override Tool groups or add one MCP server. Report format structured returns a typed {title, summary, sections} report. Defaults: 40 model calls / 80 tool calls / 1800s. Web research agent (agent.web_research) — set the Research goal. It runs live web searches via Tavily and grounds every claim in cited URLs. Leave Tavily API key blank to use the org-level TAVILY_API_KEY configuration setting (recommended). Defaults are lighter than internal research: 30 model calls / 60 tool calls / 900s. Orchestrator agent (agent.orchestrator) — set the Goal and Allowed nodes (the registry node types it may call as tools, e.g. knowledge_graph, agent.web_research, data.generate_docx). At runtime it plans with a todo list and chooses which nodes to call. Outputs include the final text, the plan it followed, and evidence (citations from report-mode node tools). Optional Require plan approval pauses the run the first time the agent proposes its todo list, before that plan is applied: a reviewer sees the proposed steps and either approves them — the agent then works through that plan — or rejects them with feedback, which goes back to the agent so it re-plans (a rejection does not end the run). The pause travels the same path as a Human review — it surfaces wherever the run is being watched, and is picked up and resolved the same way (see Human review below) — and, like Human review, it needs a checkpointed run, which is the default for a published flow. Defaults: 20 model calls / 30 tool calls / 900s. MCP action (mcp.action) — set the MCP server and a Task instruction. Choose Auth source (user = the run-user’s connection, org = organization integration, auto = user first then org), optionally whitelist tools, and set Allow writes if the task should mutate external systems. Write-capable tools are flagged in the tool list; when Allow writes is off, the builder warns which selected write tools would be dropped at run time — they are stripped so a read-only step can never mutate anything, and the run is reported as a failure rather than a false success if the task then can’t complete. On error controls failure handling: fail (halt), error_port (emit the error envelope for conditional routing on status), or continue. Defaults: 10 model calls / 15 tool calls / 300s.
MCP servers that need OAuth (Google, Slack, HubSpot…) must be connected first under Admin → MCP Servers. No-auth servers (e.g. time, sequential-thinking) work immediately.

Human nodes

Human review (human.review) — bind payload to the data you want the reviewer to see (e.g. a draft). When the run reaches this node it pauses and surfaces the item wherever the run is being watched: for a sub-agent run that means the flow-run panel in the conversation itself (the reviewer never has to leave chat), and for every run it is also listed in the Agent Inbox for staff. On approval the flow resumes; on rejection you can route back to a revise step. Set a Review title and Reviewer message for context, and choose the Review layout (inline_summary or artifact_download). Reviewer note. On the review card — whether it appears in the canvas Run panel, the in-chat flow-run panel, or the Agent Inbox (it’s one shared card) — the reviewer can add a note. The note is optional when approving and required when rejecting, so a rejection always comes back with a reason. Editing, not just approving. The review card is not read-only. Where the payload is something the card can render as fields, an Edit button turns it into an editable form: a single text value becomes a text box, and an assembled document ({title, sections}) becomes a title field plus a heading and a body box for every section. An edited card carries an edited badge and a Revert button, and the edit travels with the verdict on Approve and continue or on Reject. Outputs. Alongside approved (boolean) and response (object), the node exposes first-class ports for the reviewer’s decision and for what they actually reviewed: Downstream nodes consume the note by binding {{ nodes.<review_id>.outputs.comment }} into a prompt — e.g. a revise / LLM step that applies the feedback — and can route on {{ nodes.<review_id>.outputs.decision }} (or the existing approved). This feeds the reviewer’s feedback straight into the revise loop or the next LLM node.
Bind the next node to content, not to the draft the reviewer was shown. This is the one thing that is easy to get wrong with this block. If the DOCX node after your review gate still binds the upstream draft ($.nodes.<draft_id>.outputs.text), the reviewer’s edit is accepted and then silently ignored: the run reports success and delivers the unedited document.Bind $.nodes.<review_id>.outputs.content instead — and you can reach into it. The RFP Response Pipeline template does exactly that: its DOCX node binds title to $.nodes.review_final.outputs.content.title and sections to $.nodes.review_final.outputs.content.sections. On the un-edited path content is simply what the reviewer saw, so binding it is always correct — edit or no edit.
Not every payload can be edited. The card offers Edit only when the payload is a document ({title, sections}) or a single input value that is plain text. Bind payload to a multi-field object and the reviewer gets a read-only card; content then just echoes what they were shown. Editing is also unavailable while the full payload is still loading, so nobody can save a truncated preview over the real content.
A resume must carry a verdict. An answer to a Human review gate that is an object but carries no approve/reject verdict — a hand-rolled REST call with only a note, say — is refused rather than guessed at: the resume comes back 400 naming the gate, the review stays parked and the run stays paused. It does not take the reject branch. To reject on purpose, send approved: false, or send a bare false as the whole resume value; both are explicit verdicts and both are accepted (the bare form only when that gate is the one thing the run is waiting on). The review card always sends the verdict explicitly, so nothing changes for a reviewer working in the UI. The node itself fails closed for the same reason: an answer that reaches it with no approved key resolves to not approved.
Human review requires a checkpointed run (the engine has to be able to pause and resume) — this is the default when a flow is published and run.

Choosing the right node

A few decisions come up again and again. Here’s how to pick.

Which LLM node — classify, extract, or generate?

Rule of thumb: classify when you’ll branch on the result, extract when a downstream node (Map, Generate DOCX, a table) needs typed values, and generate for anything that should read as prose.

Map (parallel) vs Loop (sequential)

  • Map runs the same step over many items at the same time — fast, and each branch is independent (e.g. author every section of a document in parallel). Always pair it with Join to collect the results.
  • Loop repeats a step one pass at a time, up to a max count, where each pass can depend on the last (e.g. revise-until-approved, or a fixed number of retries).
Use Map for throughput over a list; use Loop for an iterative cycle that has to happen in order.

Condition + Merge (branch, then rejoin)

Use them as a pair: a Condition evaluates a value and routes the run down one of several branches; each branch does its own work; a Merge converges them back to a single path (it forwards the one branch that actually ran). Bind one input on the Merge per upstream branch. A common shape: classify → Condition → (branch A | branch B) → Merge → end.

Knowledge Graph Query vs Knowledge research vs Web research vs Orchestrator

All four can “answer a question,” but they trade off determinism, depth, and source: Rule of thumb: deterministic single question → Knowledge Graph Query; deep internal investigation → Knowledge research agent; external/current → Web research agent; unpredictable, multi-tool → Orchestrator agent. For a fixed “graph and web” answer that always runs both, prefer two parallel nodes feeding one LLM generate (deterministic, predictable cost) over an Orchestrator — see Cookbook Recipe 3.

Advanced / hidden nodes

kg.scope, kg.retrieve, and kg.report are the internal phases of Knowledge Graph Query, split into separate nodes. They’re hidden from the palette — new flows can’t add them — because the single Knowledge Graph Query node does all three phases for you. They remain registered and runnable only so older published flows that referenced them keep working. You won’t need them: use Knowledge Graph Query instead.

Where to next

  • Agent Flows Cookbook — validated, end-to-end example flows (and the patterns behind them) built from these nodes.
  • Agent Flows — the canvas basics: creating, validating, publishing, and enabling a flow as a chat sub-agent.