Abstract artistic composition
← Back to blog

Technical Guide

Multiagent Patterns

Practical workflow patterns for implementing multi-agent communication flows with conditional loops and iterative refinement.

Paulina XuJanuary 19, 202618 min
ArchitectureAgentsWorkflows

Below are example ways to represent each conversational pattern in the node-based workflow IR. Each example shows how you can stitch together the nodes and edges to achieve the desired multi-agent communication flow.

1. Basic Two-Agent Pattern with a Loop

1a. Assistant-Human: multi-turn loop

In the simplest scenario (Assistant + Human), you might want the agent to prompt the user repeatedly until some "done" condition. We can create a loop with a Conditional node:

  • start obtains an initial user input.
  • assistantAgent processes it and outputs a response (plus possibly a "continue or done?" flag).
  • deciderConditional checks if the agent is done.
  • If done, move to end.
  • Otherwise, loop back to request a new user input.

Pseudo-JSON:

{
  "version": "2.1.0",
  "workflowId": "assistant-human-loop",
  "nodes": [
    {
      "id": "start",
      "type": "Start",
      "parameters": {}
    },
    {
      "id": "assistantAgent",
      "type": "Agent",
      "configurations": {
        "agentEndpoint": "https://assistant-endpoint.example.com"
      },
      "parameters": {
        "prompt": "User said: {{globals.user_input}}; respond or say 'DONE'"
      }
    },
    {
      "id": "deciderConditional",
      "type": "Conditional",
      "configurations": {
        "callType": "SERVER" 
        // or "CLIENT" if you want the user to manually decide
      },
      "parameters": {
        "functionName": "checkIfDone",
        "agentResponse": "{{nodes.assistantAgent.output}}"
      }
    },
    {
      "id": "end",
      "type": "End"
    }
  ],
  "edges": [
    { "from": "start", "to": "assistantAgent" },
    { "from": "assistantAgent", "to": "deciderConditional" },
    // decider output routes to either 'end' or back to 'start' for new user input
    // The conditional node's function returns a nextNode = ["end"] or ["start"]
    // thus forming a cycle if it goes back to start
  ],
  "startNode": "start",
  "endNodes": ["end"]
}

Conditional logic (checkIfDone) might look at agentResponse and see if it's "DONE."

  • If DONE, return ["end"].
  • Else, return ["start"].

Because we re-trigger start, we let the user supply new input, effectively continuing the conversation.

1b. Assistant-Assistant with a cycle

Likewise, for two AI agents in a ping-pong loop:

  1. agentAagentBconditional → either loop back to agentA or end.
  2. The conditional node can see if agentB has concluded or if more back-and-forth is needed.

2. Group Chat Pattern with a Loop

For a group chat with multiple agents, you can introduce a "conditional aggregator" node that checks if the group wants to continue. If so, it loops back to the aggregator node again, redistributing all messages. If not, it goes to an end node.

Flow:

start -> aggregator -> agent1, agent2, agent3 
  -> aggregator -> deciderConditional -> aggregator or end

Pseudo-JSON:

{
  "workflowId": "group-chat-loop",
  "nodes": [
    { "id": "start", "type": "Start" },
    {
      "id": "aggregator",
      "type": "Function",
      "configurations": { "callType": "SERVER" },
      "parameters": {
        "functionName": "mergeGroupMessages",
        "incomingMessages": "{{globals.conversation_history}}"
      }
    },
    {
      "id": "agent1",
      "type": "Agent",
      "parameters": {
        "prompt": "Group chat so far: {{nodes.aggregator.output}}"
      }
    },
    {
      "id": "agent2",
      "type": "Agent",
      "parameters": {
        "prompt": "Group chat so far: {{nodes.aggregator.output}}"
      }
    },
    {
      "id": "agent3",
      "type": "Agent",
      "parameters": {
        "prompt": "Group chat so far: {{nodes.aggregator.output}}"
      }
    },
    {
      "id": "deciderConditional",
      "type": "Conditional",
      "configurations": { "callType": "SERVER" },
      "parameters": {
        "functionName": "groupContinueOrStop",
        "allAgentOutputs": [
          "{{nodes.agent1.output}}",
          "{{nodes.agent2.output}}",
          "{{nodes.agent3.output}}"
        ]
      }
    },
    { "id": "end", "type": "End" }
  ],
  "edges": [
    { "from": "start",         "to": "aggregator" },
    { "from": "aggregator",    "to": "agent1" },
    { "from": "aggregator",    "to": "agent2" },
    { "from": "aggregator",    "to": "agent3" },
    // After agents respond, go to decider
    { "from": "agent1",        "to": "deciderConditional" },
    { "from": "agent2",        "to": "deciderConditional" },
    { "from": "agent3",        "to": "deciderConditional" },
    // decider chooses to loop or end
    // if continue, returns ["aggregator"]; if stop, returns ["end"]
  ],
  "startNode": "start",
  "endNodes": ["end"]
}

Now you have a cycle: the conditional node can decide ["aggregator"] or ["end"]. If it picks "aggregator," you get a new round of group chat.

3. Chain of Thought Pattern with Repeated Steps

You can chain multiple agents in a loop until the final solution is validated. For instance:

  1. agent1agent2validatorConditional
  2. The validatorConditional can choose to re-run agent1 if the chain of thought is not sufficient.

Pseudo-JSON:

{
  "workflowId": "chain-of-thought-loop",
  "nodes": [
    { "id": "start", "type": "Start" },
    {
      "id": "agent1",
      "type": "Agent",
      "parameters": {
        "prompt": "Step 1 or next iteration. So far: {{globals.history}}"
      }
    },
    {
      "id": "agent2",
      "type": "Agent",
      "parameters": {
        "prompt": "Evaluate agent1 output: {{nodes.agent1.output}}"
      }
    },
    {
      "id": "validatorConditional",
      "type": "Conditional",
      "configurations": { "callType": "SERVER" },
      "parameters": {
        "functionName": "checkIfSolutionIsCorrect",
        "agent1Output": "{{nodes.agent1.output}}",
        "agent2Feedback": "{{nodes.agent2.output}}"
      }
    },
    { "id": "end", "type": "End" }
  ],
  "edges": [
    { "from": "start",          "to": "agent1" },
    { "from": "agent1",         "to": "agent2" },
    { "from": "agent2",         "to": "validatorConditional" },
    // If "correct," decider returns ["end"]. If "not correct," decider returns ["agent1"].
  ],
  "startNode": "start",
  "endNodes": ["end"]
}

Hence you get a cyclical re-check until it's correct or a loop limit is reached.

4. Manager-Worker Pattern with Iterative Delegation

Sometimes the manager wants to refine tasks based on worker output multiple times:

  1. managerworkerA and workerB
  2. workers produce results → a "managerDecider" conditional node checks if more iteration is needed.
  3. If more iteration is needed, loop back to manager. Otherwise, end.

Pseudo-JSON:

{
  "workflowId": "manager-worker-cycle",
  "nodes": [
    { "id": "start", "type": "Start" },
    {
      "id": "manager",
      "type": "Agent",
      "parameters": {
        "prompt": "Manager sees prior output: {{globals.managerContext}}"
      }
    },
    {
      "id": "workerA",
      "type": "Agent",
      "parameters": {
        "prompt": "Subtask A from manager: {{nodes.manager.output.subtaskA}}"
      }
    },
    {
      "id": "workerB",
      "type": "Agent",
      "parameters": {
        "prompt": "Subtask B from manager: {{nodes.manager.output.subtaskB}}"
      }
    },
    {
      "id": "managerDecider",
      "type": "Conditional",
      "parameters": {
        "functionName": "decideNextIteration",
        "resultsA": "{{nodes.workerA.output}}",
        "resultsB": "{{nodes.workerB.output}}"
      }
    },
    { "id": "end", "type": "End" }
  ],
  "edges": [
    { "from": "start",    "to": "manager" },
    { "from": "manager",  "to": "workerA" },
    { "from": "manager",  "to": "workerB" },
    { "from": "workerA",  "to": "managerDecider" },
    { "from": "workerB",  "to": "managerDecider" },
    // managerDecider can route to either "end" or back to "manager" for iteration
  ],
  "startNode": "start",
  "endNodes": [ "end" ]
}

When managerDecider decides "not done," the IR returns ["manager"]. The workflow engine sets manager node to RERUN with updated context.

5. Hierarchical Pattern with Loops

A hierarchical pattern might have multiple layers that each can loop until satisfied. For example:

  • Director delegates to TeamLead1WorkerX
  • If WorkerX's results are unsatisfactory, TeamLead1 triggers a loop back to the Worker.
  • If the TeamLead's results are incomplete, Director triggers a loop back to TeamLead1, and so on.

One possible structure:

{
  "workflowId": "hierarchy-with-loops",
  "nodes": [
    { "id": "start", "type": "Start" },
    {
      "id": "director",
      "type": "Agent",
      "parameters": {
        "prompt": "Director instructions: {{globals.user_input}}"
      }
    },
    {
      "id": "teamLead",
      "type": "Agent",
      "parameters": {
        "prompt": "TeamLead tasks from Director: {{nodes.director.output}}"
      }
    },
    {
      "id": "worker",
      "type": "Agent",
      "parameters": {
        "prompt": "Worker tasks from TeamLead: {{nodes.teamLead.output}}"
      }
    },
    {
      "id": "workerCheck",
      "type": "Conditional",
      "parameters": {
        "functionName": "checkWorkerOutput",
        "workerOutput": "{{nodes.worker.output}}"
      }
    },
    {
      "id": "leadCheck",
      "type": "Conditional",
      "parameters": {
        "functionName": "checkLeadOutput",
        "leadOutput": "{{nodes.teamLead.output}}"
      }
    },
    { "id": "end", "type": "End" }
  ],
  "edges": [
    { "from": "start",         "to": "director" },
    { "from": "director",      "to": "teamLead" },
    { "from": "teamLead",      "to": "worker" },
    { "from": "worker",        "to": "workerCheck" },
    // workerCheck either loops back to "worker" or goes on to "leadCheck"
    // if "needsMoreRefinement" => ["worker"]
    // else => ["leadCheck"]
    { "from": "workerCheck",   "to": "leadCheck" },  // when done
    // leadCheck either loops back to "teamLead" or moves on to "end" or next step
    // if "incomplete", => ["teamLead"]
    { "from": "leadCheck",     "to": "end" }
  ],
  "startNode": "start",
  "endNodes": [ "end" ]
}

Here, workerCheck might repeatedly send tasks back to worker until the worker output is accepted. Then leadCheck can repeatedly send tasks back to teamLead until overall result is accepted. Finally it goes to end.

Handling the Cycles in Practice

1. Rerun States and Loop Counters

As described in the detailed design, once a Conditional node returns a next node that has already completed, that node transitions to RERUN state. The workflow engine's cycle logic increments a loop counter. If it exceeds some maximum iteration count, the engine sets the node to FAILED to prevent infinite loops.

2. Implementation of "decider" function

Each Conditional node has a function (e.g. "checkIfDone," "decideNextIteration," "groupContinueOrStop"). That function must:

  • Inspect the relevant outputs.
  • Return either [someNodeId] or [someNodeIdA, someNodeIdB] if it wants to branch to multiple next nodes.
  • Possibly return ["end"] to stop the flow.

3. Pausing for External Input

If your design requires user input each cycle, you can have the Agent or the Conditional node produce a "requiredAction" for the client. The engine would pause until the user responds. Then the user response can be inserted into globals.user_input, and the engine re-runs the agent node.

Key Takeaways

  • Node-based workflow patterns enable flexible multi-agent coordination through conditional loops and iterative refinement.
  • Conditional nodes serve as decision points that can route execution back to previous nodes, creating powerful feedback loops.
  • Proper loop management with counters and state tracking prevents infinite loops while enabling iterative improvement.