
Technical Guide
Practical workflow patterns for implementing multi-agent communication flows with conditional loops and iterative refinement.
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.
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.end.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."
DONE, return ["end"].["start"].Because we re-trigger start, we let the user supply new input, effectively continuing the conversation.
Likewise, for two AI agents in a ping-pong loop:
agentA → agentB → conditional → either loop back to agentA or end.agentB has concluded or if more back-and-forth is needed.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.
You can chain multiple agents in a loop until the final solution is validated. For instance:
agent1 → agent2 → validatorConditionalvalidatorConditional 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.
Sometimes the manager wants to refine tasks based on worker output multiple times:
manager → workerA and workerBworkers produce results → a "managerDecider" conditional node checks if more iteration is needed.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.
A hierarchical pattern might have multiple layers that each can loop until satisfied. For example:
Director delegates to TeamLead1 → WorkerXOne 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.
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.
Each Conditional node has a function (e.g. "checkIfDone," "decideNextIteration," "groupContinueOrStop"). That function must:
[someNodeId] or [someNodeIdA, someNodeIdB] if it wants to branch to multiple next nodes.["end"] to stop the flow.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.