7 AutoGen Termination Conditions (Max Rounds, Human Approval)
Master all 7 AutoGen termination conditions including is_termination_msg, max_turns, and human approval patterns to stop agent loops reliably and safely.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Runaway agent loops are one of the first real problems you hit when building with AutoGen. An agent gets stuck, starts repeating itself, or keeps requesting clarifications in a loop — and your token bill grows with every iteration. Getting termination conditions right is not a nice-to-have. It's foundational.
I've debugged enough infinite loops to know that the fix is almost always one of these seven patterns. Some are simple one-liners. Some require a bit of logic. All of them are worth knowing.
The patterns here build on the core AutoGen agent setup. If you're new to the framework, AI agents explained covers the basics, and Build AI agent with LangChain gives a comparison point from the LangChain side.
Why Termination Is Non-Trivial
The challenge with agent termination is that you often don't know in advance exactly what the final message will look like. The agent might say "Task complete", "I've finished", "Done!", or "The analysis is complete." String matching alone gets brittle.
Add multi-agent group chats to the mix — where multiple agents interact — and it becomes even harder. You need to decide: does a termination trigger from any agent end the whole conversation, or only when a specific agent says it?
AutoGen gives you seven distinct mechanisms. Using them in combination is how you build robust stopping behavior.
Base Setup for All Examples
import autogen
import os
from dotenv import load_dotenv
load_dotenv()
llm_config = {
"config_list": [
{"model": "gpt-4o", "api_key": os.getenv("OPENAI_API_KEY")}
],
"temperature": 0,
}
assistant = autogen.AssistantAgent(
name="assistant",
llm_config=llm_config,
system_message="Complete tasks efficiently. When done, say 'TASK_COMPLETE' at the start of your final message.",
)
Condition 1: is_termination_msg
The most common pattern. Pass a function that inspects the last message and returns True to stop.
def is_done(message: dict) -> bool:
"""Stop when the assistant signals task completion."""
content = message.get("content", "") or ""
return content.strip().startswith("TASK_COMPLETE")
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
is_termination_msg=is_done,
code_execution_config=False,
)
user_proxy.initiate_chat(
assistant,
message="List the top 3 programming languages by popularity in 2026.",
)
The function receives the full message dict. This gives you access to role, name, content, and any tool call information — more than just the text.
More sophisticated version with multiple trigger phrases:
TERMINATION_SIGNALS = [
"TASK_COMPLETE",
"FINISHED",
"NO_FURTHER_ACTIONS",
"ANALYSIS_COMPLETE",
]
def is_done_flexible(message: dict) -> bool:
content = (message.get("content") or "").upper()
return any(signal in content for signal in TERMINATION_SIGNALS)
Condition 2: max_turns in initiate_chat
The simplest hard limit. The conversation ends after N total exchanges.
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
code_execution_config=False,
)
user_proxy.initiate_chat(
assistant,
message="Analyze the pros and cons of Python vs Go for backend development.",
max_turns=6, # Hard stop after 6 turns
)
max_turns counts round-trips: user sends, assistant responds = 1 turn. Setting max_turns=6 gives you roughly 3 user messages and 3 assistant responses.
Use max_turns as a safety net even when you have other termination conditions. It prevents infinite loops if your primary condition fails.
Condition 3: max_consecutive_auto_reply
Different from max_turns — this counts how many times the UserProxyAgent responds without human intervention.
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
max_consecutive_auto_reply=5, # Stop after 5 auto-replies
code_execution_config=False,
)
When human_input_mode="NEVER", hitting this limit stops the conversation entirely. When human_input_mode="TERMINATE", it prompts the human at the limit and continues if they respond.
This is the default safety valve in AutoGen. The default value is 10, which means even without explicit termination, conversations will stop after 10 proxy replies.
Condition 4: Human Approval Mode
For production systems where a human should review before the agent continues:
user_proxy_with_approval = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="ALWAYS", # Ask human before every action
code_execution_config={
"use_docker": False,
"work_dir": "agent_workspace",
},
)
user_proxy_with_approval.initiate_chat(
assistant,
message="Write and execute a Python script to analyze the sales data in sales.csv",
)
With "ALWAYS", the proxy pauses after every assistant message and waits for human input. The human can:
- Press Enter to continue (accept the agent's plan)
- Type
exitto terminate - Type a correction or new instruction
For selective approval — only pausing before risky actions — use "TERMINATE" mode with a custom check:
RISKY_KEYWORDS = ["delete", "remove", "drop table", "rm -", "format"]
def should_ask_human(message: dict) -> bool:
content = (message.get("content") or "").lower()
return any(keyword in content for keyword in RISKY_KEYWORDS)
user_proxy_selective = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="TERMINATE",
is_termination_msg=should_ask_human,
code_execution_config=False,
)
This stops for human review whenever the assistant mentions potentially destructive operations.
Condition 5: Custom Termination Function with State
Sometimes you need termination logic that tracks state across messages. A closure works well here:
def make_task_counter_termination(max_tasks: int):
"""Terminate after the assistant completes N distinct tasks."""
completed = {"count": 0}
def check_termination(message: dict) -> bool:
content = message.get("content", "") or ""
if "TASK_COMPLETE" in content:
completed["count"] += 1
print(f"Task {completed['count']}/{max_tasks} completed")
return completed["count"] >= max_tasks
return check_termination
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
is_termination_msg=make_task_counter_termination(max_tasks=3),
code_execution_config=False,
)
user_proxy.initiate_chat(
assistant,
message="""Complete these three tasks in sequence:
1. List the top 5 Python web frameworks
2. Compare FastAPI and Django in 100 words
3. Recommend which to use for a microservice API
Signal TASK_COMPLETE after each task.""",
)
The closure maintains a counter that the termination function can update and check.
Condition 6: GroupChat-Level Termination
In group chats with multiple agents, termination works at the GroupChatManager level:
researcher = autogen.AssistantAgent(
name="Researcher",
llm_config=llm_config,
system_message="Research topics thoroughly. When research is complete, start your message with 'RESEARCH_DONE:'",
)
writer = autogen.AssistantAgent(
name="Writer",
llm_config=llm_config,
system_message="Write clear summaries. When writing is complete, start your message with 'WRITING_DONE:'",
)
critic = autogen.AssistantAgent(
name="Critic",
llm_config=llm_config,
system_message="Review and approve content. When satisfied, say 'APPROVED: content is ready.'",
)
user_proxy = autogen.UserProxyAgent(
name="Manager",
human_input_mode="NEVER",
is_termination_msg=lambda msg: "APPROVED:" in (msg.get("content") or ""),
code_execution_config=False,
)
groupchat = autogen.GroupChat(
agents=[researcher, writer, critic, user_proxy],
messages=[],
max_round=15, # GroupChat-level max rounds
)
manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=llm_config,
)
user_proxy.initiate_chat(
manager,
message="Research and write a 300-word summary of quantum computing trends in 2026.",
)
The max_round in GroupChat is separate from max_turns in initiate_chat. Both apply — the conversation stops at whichever triggers first.
Condition 7: Timeout-Based Termination
AutoGen doesn't have a built-in wall-clock timeout, but you can implement one:
import threading
import time
class TimeoutAgent(autogen.UserProxyAgent):
def __init__(self, timeout_seconds: int, *args, **kwargs):
super().__init__(*args, **kwargs)
self.timeout_seconds = timeout_seconds
self._start_time = None
self._timed_out = False
def initiate_chat(self, recipient, **kwargs):
self._start_time = time.time()
return super().initiate_chat(recipient, **kwargs)
def check_termination_and_human_reply(self, messages, sender, config):
elapsed = time.time() - (self._start_time or time.time())
if elapsed > self.timeout_seconds:
print(f"Timeout: {elapsed:.1f}s elapsed, stopping conversation.")
return True, "TIMEOUT: Conversation exceeded time limit."
return super().check_termination_and_human_reply(messages, sender, config)
timeout_proxy = TimeoutAgent(
timeout_seconds=120, # 2-minute limit
name="user_proxy",
human_input_mode="NEVER",
code_execution_config=False,
)
Timeout termination is especially important for agents making external API calls — a slow or hung network request can block execution indefinitely without it.
Termination Condition Comparison
| Condition | Use Case | Granularity | Reliability |
|---|---|---|---|
is_termination_msg | Task completion | Message-level | High (depends on prompt) |
max_turns | Hard safety limit | Turn-level | Very high |
max_consecutive_auto_reply | Proxy limit | Reply-level | Very high |
| Human approval (ALWAYS) | Sensitive tasks | Action-level | Human-dependent |
| Human approval (TERMINATE) | Risky operations | Keyword-level | High |
| Stateful termination | Multi-task workflows | Task-level | High |
| GroupChat max_round | Group conversations | Round-level | Very high |
| Timeout | Long-running tasks | Time-based | Very high |
Best Practices for Production
Always layer conditions. Use is_termination_msg as your primary condition and max_turns as a fallback. Never rely on only one.
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
is_termination_msg=lambda msg: "TASK_COMPLETE" in (msg.get("content") or ""),
max_consecutive_auto_reply=8, # Fallback
code_execution_config=False,
)
user_proxy.initiate_chat(
assistant,
message="Complete the analysis task.",
max_turns=12, # Additional fallback
)
Be explicit in your system message. The termination signal only works if the agent reliably produces it. Your system message should clearly specify what the signal is and when to use it.
Log termination reason. In production, knowing why a conversation stopped is important for debugging:
def is_done_with_logging(message: dict) -> bool:
content = message.get("content") or ""
if "TASK_COMPLETE" in content:
print(f"[TERMINATION] Signal found in message from {message.get('name')}")
return True
return False
Test termination paths explicitly. Before deploying, run scenarios designed to trigger each termination condition and verify they work as expected.
For memory and state that persists after termination, the patterns in AI agent memory and planning complement what's covered here. And for a complete agentic system that uses these termination patterns in context, CrewAI tutorial shows how structured workflows handle termination at the task level.
FAQ
What happens if AutoGen never terminates?
Without a termination condition, AutoGen will keep running until it hits the max_consecutive_auto_reply limit (default 10) on the UserProxyAgent. After that it will prompt for human input or stop depending on human_input_mode. Always set explicit termination to avoid unexpected costs.
Can I combine multiple termination conditions in AutoGen?
Yes. Multiple conditions are evaluated together and the conversation ends when any one of them is met. You can combine is_termination_msg with max_turns and a custom condition function, and the conversation will stop at the first trigger.
How does is_termination_msg work exactly?
is_termination_msg is a function that receives the last message dictionary and returns True if the conversation should stop. AutoGen checks this function after every message. A common pattern is checking if a specific string like "TASK_COMPLETE" appears in the message content.
What is the difference between max_turns and max_consecutive_auto_reply?
max_turns is set on the initiate_chat call and limits the total number of conversation rounds. max_consecutive_auto_reply is set on the UserProxyAgent and limits how many times the proxy replies without human input. Both can stop the conversation but they count differently.
How do I implement human approval before a tool executes in AutoGen?
Set human_input_mode='ALWAYS' on the UserProxyAgent. This pauses execution before every tool call or code execution and waits for human input. For selective approval (only certain tools), use a custom is_termination_msg function that checks what the agent is about to do.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
5 AutoGen Agent Roles (Assistant, UserProxy, CodeExecutor)
Understand the 5 core AutoGen agent types — AssistantAgent, UserProxyAgent, CodeExecutorAgent, and more — with code examples and a comparison table for each role.
How to Deploy AutoGen Agents as APIs with FastAPI (2026)
Learn to serve AutoGen multi-agent systems as production REST APIs using FastAPI with async endpoints and real-time streaming responses.
How to Use AutoGen with Azure OpenAI (Enterprise Security)
Connect Microsoft AutoGen to Azure OpenAI for enterprise-grade AI agents. Step-by-step setup with private endpoints, OAI_CONFIG_LIST, and deployment config.
Build a Code Debugging Agent with AutoGen (Auto-Fix PRs)
Build an AutoGen agent that reviews code, analyzes PR diffs, suggests fixes, and automates code quality improvements with a full working implementation.