Skip to main content
A flow config describes a conversation as data: the nodes, what each one says, which tools each node offers, and where each tool leads. It contains no Python. The tools it names live in your code and are resolved when the config is joined to your handlers. Writing the flow this way puts a seam between the graph and the bot. One deployed bot can run whichever flow a session calls for, loaded from a file, a database, or a CMS, and someone who is not an engineer can change what the bot says or where a step leads without a deploy.

The Document

A config has an initial_node, a map of nodes keyed by name, and an optional list of global_functions. Here is the food-ordering example in full:
flow.yaml
The full example, with its complete prompts and its handlers.py, is in examples/flows/yaml/food_ordering/.

Top-Level Keys

str
required
Name of the node the flow starts in. Must be a key of nodes.
dict[str, Node]
required
The flow’s nodes, keyed by name. The key is the node’s name — it is what initial_node and every transition_to refer to. At least one is required.
list[Function]
Tools offered at every node, written the same way as a node’s functions. A name used here can’t also appear in a node’s functions.

Node Keys

list[Message]
required
What the LLM should do at this node. Each entry has a role (such as developer or system) and a content string.
str
The bot’s role or personality, sent as the LLM’s system instruction on entering this node. It persists across transitions until another node sets its own.
list[Function]
Tools offered at this node, in addition to the config’s global_functions. See Functions for the entry format, transition_only, and branch tables.
list[Action]
Actions run before the LLM responds at this node. See Actions.
list[Action]
Actions run after the LLM responds at this node.
"append" | "reset"
How the LLM context is updated on entering this node. Defaults to the FlowManager’s strategy. See Context Strategies.
bool
default:"true"
Whether the LLM responds as soon as the node is entered.
role_message and each task message’s content may contain {{ key }} placeholders, filled from the manager’s state each time the node is entered. See Placeholders.

Loading a Config

FlowConfig is a Pydantic model, so model_validate is the loader for a dict you built or fetched yourself.

Splitting Out Long Prompts

A YAML config loaded with from_file can pull text in from another file with !include, resolved relative to the config’s own directory. This keeps a long prompt out of the graph:
!include is available with from_file, and with from_yaml when you pass base_dir. It is not available in JSON.

Validation

A config is checked in two stages, and between them everything is checked. On load, its structure: the top level is a mapping, initial_node names a defined node, every transition_to names a defined node, tool names are unique within a node and across global_functions, a transition_only entry has both a description and a plain node name to transition to, an ordinary entry has no description, and every action is well-formed. A failure raises a Pydantic ValidationError. When the Flow is constructed, its references into your code: every tool it names exists in the handlers, is callable, and is a valid direct function; every action handler it names exists. These are collected and reported together as a single FlowReferenceError, rather than stopping at the first, so one run surfaces every miss.
Each problem is also available on the exception as a FlowProblem with a stable code, so you can handle them programmatically.
Because both stages run before the first call comes in, starting the bot once is a complete check of the flow.

Joining a Config to Code

A Flow is a config joined to the Python it names:
handlers can be:
  • A module, as above — the usual case. Its top-level functions are looked up by name.
  • A mapping of names to callables, when you want to build the namespace yourself or expose a tool under a different name than the function has.
  • A list or tuple of either, when tools and action handlers live in separate modules.
Only the names the config actually references are looked up, so an unrelated function in the module is ignored. With a list, a name that resolves to different callables in more than one entry is an error rather than a silent choice; the same callable reachable through two of them is fine. The flow then hands three things to the FlowManager:
NodeConfig
The node the flow starts in, ready to pass to FlowManager.initialize().
list
The config’s global functions, ready to pass to FlowManager(global_functions=...). A fresh list each time, so you can extend it with tools defined in code.
NodeConfig
Any node by name, for the rare case where code needs to jump into the graph directly. Raises FlowError if the config has no such node.

Loading a Flow Per Session

Because the config is just a document, the bot doesn’t have to ship with it. Fetch the flow a session calls for, seed the facts its prompts refer to, and initialize:
The handlers stay the same across every flow the bot can run. What changes per session is the document: which nodes exist, what they say, and where each tool leads.
A config fetched from outside your codebase can still name a tool your handlers don’t have. Construct the Flow where you can catch FlowReferenceError and fail the session cleanly, rather than mid-call.

Tooling

The config format’s JSON Schema is published in the Pipecat repository at src/pipecat/flows/flow_config.schema.json. Point your editor at it for completion and inline validation while writing a config, or vendor it into a tool that generates one. The Pipecat Flows Visual Editor lets you design a flow visually rather than by hand.

Next Steps

Functions

Tool entries, transition-only functions, and branch tables

State Management

Placeholders, cross-node state, and global functions

Actions

Built-in and custom actions in a config

FlowConfig Reference

Every field, loader, and validation rule