@@ -1582,6 +1582,7 @@ def _build_prompt(
15821582 task : str ,
15831583 prev_outputs : Dict [str , Any ],
15841584 layer_idx : int ,
1585+ loop_idx : int = 0 ,
15851586 ) -> str :
15861587 """
15871588 Optimized prompt building with minimal string operations.
@@ -1590,14 +1591,17 @@ def _build_prompt(
15901591 node_id (str): The node ID to build a prompt for.
15911592 task (str): The main task.
15921593 prev_outputs (Dict[str, Any]): Previous outputs from predecessor nodes.
1594+ For loop_idx > 0 this also contains end-point outputs from the
1595+ previous loop iteration.
15931596 layer_idx (int): The current layer index.
1597+ loop_idx (int): The current loop iteration (0-based).
15941598
15951599 Returns:
15961600 str: The built prompt.
15971601 """
15981602 if self .verbose :
15991603 logger .debug (
1600- f"Building prompt for node { node_id } (layer { layer_idx } )"
1604+ f"Building prompt for node { node_id } (layer { layer_idx } , loop { loop_idx } )"
16011605 )
16021606
16031607 try :
@@ -1626,6 +1630,24 @@ def _build_prompt(
16261630 f"If you disagree or find gaps, explain why and provide corrections or improvements. "
16271631 f"Your goal is to collaborate and create a comprehensive response that builds on all previous work."
16281632 )
1633+ elif loop_idx > 0 and layer_idx == 0 and prev_outputs :
1634+ # Entry-point nodes in subsequent loops receive end-point
1635+ # outputs from the previous loop as refinement context.
1636+ prior_parts = [
1637+ f"Output from { nid } (previous iteration):\n { out } "
1638+ for nid , out in prev_outputs .items ()
1639+ if out is not None
1640+ ]
1641+ prior_context = "\n \n " .join (prior_parts )
1642+
1643+ prompt = (
1644+ f"Original Task: { task } \n \n "
1645+ f"Previous Iteration Outputs:\n { prior_context } \n \n "
1646+ f"Instructions: This is iteration { loop_idx + 1 } of the workflow. "
1647+ f"Review the outputs from the previous iteration above. "
1648+ f"Refine, correct, or expand upon the previous results. "
1649+ f"Focus on improving accuracy, filling gaps, and strengthening the analysis."
1650+ )
16291651 else :
16301652 prompt = (
16311653 f"{ task } \n \n "
@@ -1691,14 +1713,22 @@ def run(
16911713 """
16921714 Run the workflow graph with optimized parallel agent execution.
16931715
1716+ When max_loops > 1, the graph is executed multiple times. End-point
1717+ outputs from each loop are fed as additional context into the next
1718+ loop so that agents can iteratively refine their results.
1719+
16941720 Args:
16951721 task (Optional[str]): Task to execute. Uses self.task if not provided.
16961722 img (Optional[str]): Optional image path for multimodal tasks.
16971723 *args: Additional positional arguments.
16981724 **kwargs: Additional keyword arguments.
16991725
17001726 Returns:
1701- Dict[str, Any]: Execution results from all nodes.
1727+ Dict[str, Any]: Execution results keyed by node ID.
1728+ When max_loops == 1, returns the single loop's results.
1729+ When max_loops > 1, returns a dict with per-loop results
1730+ keyed as ``{node_id}_loop_{loop_number}`` plus the final
1731+ loop's results under the plain ``node_id`` keys.
17021732 """
17031733 run_start_time = time .time ()
17041734
@@ -1731,6 +1761,11 @@ def run(
17311761
17321762 try :
17331763 loop = 0
1764+ # Accumulated results across all loops
1765+ all_loop_results : Dict [str , Any ] = {}
1766+ # End-point outputs carried forward as context for the next loop
1767+ prior_loop_end_outputs : Dict [str , Any ] = {}
1768+
17341769 while loop < self .max_loops :
17351770 loop_start_time = time .time ()
17361771
@@ -1747,6 +1782,11 @@ def run(
17471782 execution_results = {}
17481783 prev_outputs = {}
17491784
1785+ # Seed entry-point nodes with end-point outputs from the
1786+ # previous loop so agents can refine iteratively.
1787+ if prior_loop_end_outputs :
1788+ prev_outputs .update (prior_loop_end_outputs )
1789+
17501790 for layer_idx , layer in enumerate (
17511791 self ._sorted_layers
17521792 ):
@@ -1762,7 +1802,7 @@ def run(
17621802 for node_id in layer :
17631803 try :
17641804 prompt = self ._build_prompt (
1765- node_id , task , prev_outputs , layer_idx
1805+ node_id , task , prev_outputs , layer_idx , loop
17661806 )
17671807 layer_data .append (
17681808 (
@@ -1894,20 +1934,40 @@ def run(
18941934 f"Loop { loop } /{ self .max_loops } completed in { loop_execution_time :.3f} s"
18951935 )
18961936
1897- # For now, we still return after the first loop
1898- # This maintains backward compatibility
1899- total_execution_time = time .time () - run_start_time
1937+ # Capture end-point outputs to pass as context into the next loop
1938+ prior_loop_end_outputs = {
1939+ node_id : execution_results [node_id ]
1940+ for node_id in self .end_points
1941+ if node_id in execution_results
1942+ }
19001943
1901- logger .info (
1902- f"GraphWorkflow execution completed: { len (execution_results )} agents executed in { total_execution_time :.3f} s"
1944+ # Accumulate per-loop results (keyed to avoid overwriting)
1945+ if self .max_loops > 1 :
1946+ for node_id , output in execution_results .items ():
1947+ all_loop_results [
1948+ f"{ node_id } _loop_{ loop } "
1949+ ] = output
1950+
1951+ # Build final return value
1952+ total_execution_time = time .time () - run_start_time
1953+
1954+ logger .info (
1955+ f"GraphWorkflow execution completed: { len (execution_results )} agents executed across { self .max_loops } loop(s) in { total_execution_time :.3f} s"
1956+ )
1957+
1958+ if self .verbose :
1959+ logger .debug (
1960+ f"Final execution results: { list (execution_results .keys ())} "
19031961 )
19041962
1905- if self .verbose :
1906- logger .debug (
1907- f"Final execution results: { list (execution_results .keys ())} "
1908- )
1963+ # For single-loop (the common case), return results directly.
1964+ # For multi-loop, merge the per-loop history with the final
1965+ # loop's results so callers can access both.
1966+ if self .max_loops > 1 :
1967+ all_loop_results .update (execution_results )
1968+ return all_loop_results
19091969
1910- return execution_results
1970+ return execution_results
19111971
19121972 except Exception as e :
19131973 total_time = time .time () - run_start_time
0 commit comments