pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

Dynamic flows

Build each node at runtime from what the conversation has learned.

In a dynamic flow, nodes are functions that return a NodeConfig, and a tool's handler returns the node to go to next. Because the next node is built when the tool is called, it can depend on arguments, state or anything else you can compute.

This flow books a slot in three steps. The hour node is built for the day the caller picked, and the last node confirms the slot and ends the call:

interface Slot {
  day: string;
  hour: number;
}

function createDayNode(): NodeConfig {
  return {
    name: "day",
    taskMessages: [{ role: "developer", content: "Ask which weekday the caller prefers." }],
    functions: [
      new FlowsFunctionSchema({
        name: "choose_day",
        description: "Record the preferred weekday.",
        properties: { day: { type: "string" } },
        required: ["day"],
        handler: async (args, fm) => {
          fm.state.day = args.day;
          // Build the next node from what the caller just said.
          return [{ day: args.day }, createHourNode(String(args.day))];
        },
      }),
    ],
  };
}

function createHourNode(day: string): NodeConfig {
  return {
    name: "hour",
    taskMessages: [{ role: "developer", content: `Offer times on ${day} and ask which one.` }],
    functions: [
      new FlowsFunctionSchema({
        name: "choose_hour",
        description: "Record the chosen hour.",
        properties: { hour: { type: "integer" } },
        required: ["hour"],
        handler: async (args, fm) => {
          const slot: Slot = { day, hour: Number(args.hour) };
          fm.state.slot = slot;
          return [slot, createDoneNode(slot)];
        },
      }),
    ],
  };
}

function createDoneNode(slot: Slot): NodeConfig {
  return {
    name: "done",
    taskMessages: [
      { role: "developer", content: `Confirm ${slot.day} at ${slot.hour}:00, then say goodbye.` },
    ],
    postActions: [{ type: "end_conversation" }],
  };
}

await flowManager.setNodeFromConfig(createDayNode());

Each handler returns [result, nextNode]. The result goes back to the LLM as the tool result; the manager then enters the next node, which swaps in its task messages and tools and asks the LLM to respond. A handler can return null as the node to stay put, or NO_RESPONSE to stay quiet.

Start it with flowManager.initialize(createDayNode()), or enter it later with setNodeFromConfig. Use flow configs instead when the graph is fixed and you want it reviewable as data.

pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.

Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).