{"record":{"id":"ed12dce2c6e9ba70","repo":"microsoft/autogen","slug":"a-termination-condition-is-required-for-cyclic-gra","errorCode":null,"errorMessage":"A termination condition is required for cyclic graphs without a maximum turn limit.","messagePattern":"A termination condition is required for cyclic graphs without a maximum turn limit\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_graph/_digraph_group_chat.py","lineNumber":341,"sourceCode":"        message_factory: MessageFactory,\n        graph: DiGraph,\n    ) -> None:\n        \"\"\"Initialize the graph-based execution manager.\"\"\"\n        super().__init__(\n            name=name,\n            group_topic_type=group_topic_type,\n            output_topic_type=output_topic_type,\n            participant_topic_types=participant_topic_types,\n            participant_names=participant_names,\n            participant_descriptions=participant_descriptions,\n            output_message_queue=output_message_queue,\n            termination_condition=termination_condition,\n            max_turns=max_turns,\n            message_factory=message_factory,\n        )\n        graph.graph_validate()\n        if graph.get_has_cycles() and self._termination_condition is None and self._max_turns is None:\n            raise ValueError(\"A termination condition is required for cyclic graphs without a maximum turn limit.\")\n        self._graph = graph\n        # Lookup table for incoming edges for each node.\n        self._parents = graph.get_parents()\n        # Lookup table for outgoing edges for each node.\n        self._edges: Dict[str, List[DiGraphEdge]] = {n: node.edges for n, node in graph.nodes.items()}\n\n        # Build activation and enqueued_any lookup tables by collecting all edges and grouping by target node\n        self._build_lookup_tables(graph)\n\n        # Track which activation groups were triggered for each node\n        self._triggered_activation_groups: Dict[str, Set[str]] = {}\n        # === Mutable states for the graph execution ===\n        # Count the number of remaining parents to activate each node.\n        self._remaining: Dict[str, Counter[str]] = {\n            target: Counter(groups) for target, groups in graph.get_remaining_map().items()\n        }\n        # cache for remaining\n        self._origin_remaining: Dict[str, Dict[str, int]] = {","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_graph/_digraph_group_chat.py#L323-L359","documentation":"Raised when constructing a graph-based flow whose graph contains a (valid, conditional) cycle but the team has neither a termination_condition nor max_turns. A cyclic graph can run forever; the library requires an explicit stopping mechanism — a TerminationCondition or a turn cap — before it will run the loop.","triggerScenarios":"GraphFlow(participants, ...) with a conditional loop in the graph and no termination_condition= and no max_turns= arguments; removing a termination condition during debugging; porting a linear graph to cyclic without adding stopping criteria.","commonSituations":"Draft/review/revise loops; teams built from config where the termination section is optional and omitted; interactive workflows where the author assumed the loop's conditions alone would stop it.","solutions":["Pass a termination_condition such as MaxMessageTermination(n) or TextMentionTermination(\"APPROVED\") to GraphFlow.","Or set max_turns=N to hard-cap the number of turns.","Prefer both: a semantic termination condition plus a generous max_turns safety net."],"exampleFix":"// before\nflow = GraphFlow([draft, review], graph_builder=builder)  # graph has a cycle\n\n// after\nfrom autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination\nflow = GraphFlow([draft, review], graph_builder=builder,\n    termination_condition=TextMentionTermination(\"APPROVED\") | MaxMessageTermination(20))","handlingStrategy":"validation","validationCode":"graph = builder.build()\nif graph.get_has_cycles() and termination_condition is None and max_turns is None:\n    raise ValueError(\"Cyclic graph needs termination_condition or max_turns\")\nflow = GraphFlow(participants, graph_builder=builder,\n                 termination_condition=termination_condition, max_turns=max_turns)","typeGuard":"def flow_has_stop_criterion(has_cycles: bool, termination_condition, max_turns) -> bool:\n    return not has_cycles or termination_condition is not None or max_turns is not None","tryCatchPattern":"try:\n    flow = GraphFlow(participants, graph_builder=builder)\nexcept ValueError as e:\n    if \"termination condition is required\" in str(e):\n        # add termination_condition=TextMentionTermination('APPROVED') or max_turns=N\n        ...","preventionTips":["Always pair cyclic graphs with a semantic termination condition plus a max_turns cap.","Choose termination phrases the agents can actually emit (and document them in prompts).","Treat a missing stop criterion as a build error in CI for graph workflows."],"tags":["graph","termination","cycles","configuration"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}