5 AutoGen Human Input Modes (Always, Never, Sometimes)
Master AutoGen's human input modes for hybrid autonomy. Learn when to use ALWAYS, NEVER, and TERMINATE with real code examples and a comparison table.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
The difference between an AI agent that's useful and one that's dangerous often comes down to a single design decision: when does a human get to intervene?
AutoGen built its answer into the UserProxyAgent class through the human_input_mode parameter. This one setting controls whether your agent runs autonomously, pauses for approval at every step, or uses a hybrid model that combines speed with oversight. Getting this right is the difference between a demo and a production system.
This guide covers all five meaningful configurations of human input in AutoGen — including the three named modes, a custom callable pattern, and a conditional approach — with complete code examples and an honest table of tradeoffs.
The Architecture Behind Human Input
Before looking at modes, understand the structure. AutoGen separates concerns into two agent types:
AssistantAgent — the LLM-powered reasoner. It generates text, calls tools, and responds to messages. It doesn't handle human interaction directly.
UserProxyAgent — the interface between humans and the agent system. It optionally executes code, manages conversation flow, and handles human input collection. The human_input_mode lives here.
import autogen
llm_config = {
"config_list": [{"model": "gpt-4o", "api_key": "your-key"}],
"temperature": 0.1
}
assistant = autogen.AssistantAgent(
name="Assistant",
llm_config=llm_config
)
# human_input_mode is the key parameter
user_proxy = autogen.UserProxyAgent(
name="User",
human_input_mode="TERMINATE", # ← This controls everything
max_consecutive_auto_reply=10
)
Mode 1: ALWAYS — Maximum Human Control
ALWAYS mode requests human input after every single message from the assistant. The agent never takes a second step without human confirmation.
user_proxy_always = autogen.UserProxyAgent(
name="Careful_User",
human_input_mode="ALWAYS",
max_consecutive_auto_reply=0, # Never auto-reply — always ask
code_execution_config=False # No code execution in this example
)
assistant.reset()
user_proxy_always.initiate_chat(
assistant,
message="Draft an email to our top 10 customers about the price change."
)
When you run this, you'll see:
Assistant (to Careful_User):
[Draft email content...]
Provide feedback to Careful_User. Press enter to skip and use auto-reply,
or type 'exit' to stop the conversation:
The agent pauses and waits. You can:
- Press Enter to accept the response and have the agent continue
- Type a message to give feedback ("make it shorter", "add a subject line")
- Type "exit" to end the conversation
When ALWAYS makes sense:
- Sensitive communications (customer emails, financial reports)
- Any action that's irreversible or high-stakes
- Training and learning scenarios where you want to observe every step
- Debugging new agent workflows before trusting them
Practical limitation: ALWAYS mode destroys the value of automation for any workflow longer than 3-4 steps. If you're approving every message, you could write the responses yourself.
Mode 2: NEVER — Full Automation
NEVER mode runs the entire conversation without requesting human input. The agent self-directs from the initial message to the termination condition.
user_proxy_never = autogen.UserProxyAgent(
name="Automated_User",
human_input_mode="NEVER",
max_consecutive_auto_reply=15,
code_execution_config={
"work_dir": "output",
"use_docker": False
},
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE")
)
user_proxy_never.initiate_chat(
assistant,
message="""Analyze the file data.csv and produce:
1. Summary statistics
2. A bar chart saved as chart.png
3. A written summary saved as report.txt
End with TERMINATE when complete."""
)
# Runs to completion with no human prompts
The max_consecutive_auto_reply acts as a safety valve. If the agent exceeds this number of turns, the conversation ends. Set it based on your task complexity — research tasks might need 20+, simple queries can use 5.
When NEVER makes sense:
- Batch processing pipelines (analyze 500 documents overnight)
- Cron-based automation (daily reports, monitoring agents)
- Tasks with low blast radius (read-only analysis, drafting)
- Well-tested workflows where you've already validated the behavior
The risk with NEVER: the agent can take unintended actions with no checkpoint. If your agent has write access to a database or can send emails, NEVER mode means a bad run can cause real damage before you notice.
Mode 3: TERMINATE — The Hybrid Sweet Spot
TERMINATE mode is the default for most production systems. The agent runs autonomously until either:
- A message matches the
is_termination_msgcondition max_consecutive_auto_replyis reached
At that point, it pauses for human input, then resets the counter and continues.
def enterprise_termination(message: dict) -> bool:
"""Trigger human review for completion signals or error states."""
content = message.get("content", "")
return (
content.rstrip().endswith("TERMINATE") or
"NEEDS_REVIEW" in content or
"ERROR:" in content
)
user_proxy_terminate = autogen.UserProxyAgent(
name="Enterprise_User",
human_input_mode="TERMINATE",
max_consecutive_auto_reply=8,
code_execution_config={
"work_dir": "workspace",
"use_docker": True
},
is_termination_msg=enterprise_termination
)
user_proxy_terminate.initiate_chat(
assistant,
message="Process the invoices in /data/invoices/ and flag any anomalies. Say TERMINATE when done or NEEDS_REVIEW if you find anything suspicious."
)
The agent works through the invoices autonomously. If it finds something suspicious, it says "NEEDS_REVIEW" and pauses for human judgment. Otherwise it processes everything and terminates cleanly.
This pattern is particularly powerful for agents that need to handle the routine cases automatically but escalate edge cases — exactly how a junior analyst might work with a senior.
Mode 4: Custom Callable — Conditional Human Input
For more sophisticated workflows, replace the string mode with a function. This pattern isn't officially a "fifth mode" in the docs, but it's how you build truly adaptive human oversight.
from typing import Optional
import re
class AdaptiveInputController:
"""Controls human input based on content sensitivity and step count."""
def __init__(self, sensitive_keywords: list, max_auto_steps: int = 5):
self.sensitive_keywords = sensitive_keywords
self.max_auto_steps = max_auto_steps
self.auto_step_count = 0
def should_request_input(self, message: dict) -> bool:
content = message.get("content", "").lower()
# Always ask if sensitive content detected
for keyword in self.sensitive_keywords:
if keyword.lower() in content:
print(f"\n[OVERSIGHT] Sensitive keyword '{keyword}' detected.")
self.auto_step_count = 0
return True
# Ask every N auto-steps
self.auto_step_count += 1
if self.auto_step_count >= self.max_auto_steps:
print(f"\n[OVERSIGHT] {self.max_auto_steps}-step checkpoint reached.")
self.auto_step_count = 0
return True
return False
controller = AdaptiveInputController(
sensitive_keywords=["delete", "drop table", "send email", "production"],
max_auto_steps=5
)
# UserProxyAgent with custom input handler
class AdaptiveUserProxy(autogen.UserProxyAgent):
def __init__(self, controller: AdaptiveInputController, **kwargs):
super().__init__(**kwargs)
self.controller = controller
def get_human_input(self, prompt: str) -> str:
"""Override to use controller logic."""
# In a real system, this could query an approval workflow
return input(prompt)
def check_termination_and_human_reply(self, messages, sender, config=None):
"""Check if we need human input based on controller logic."""
if messages:
last_message = messages[-1] if isinstance(messages[-1], dict) else {}
if self.controller.should_request_input(last_message):
human_reply = self.get_human_input(
"Agent action requires approval. Press Enter to continue, "
"or type instructions: "
)
if human_reply.strip():
return True, human_reply
return super().check_termination_and_human_reply(messages, sender, config)
adaptive_proxy = AdaptiveUserProxy(
controller=controller,
name="Adaptive_User",
human_input_mode="NEVER", # Base mode — controller overrides
max_consecutive_auto_reply=20
)
This approach gives you human oversight that's triggered by content semantics, not just turn counts — far more useful for real workloads.
Mode 5: Programmatic Human Input — API and Webhook Patterns
The fifth pattern removes the human from the terminal entirely and routes approvals through external systems — Slack, email, a web UI, or an approval API.
import autogen
import requests
import time
class SlackApprovalProxy(autogen.UserProxyAgent):
"""Routes human approval requests to Slack."""
def __init__(self, slack_webhook: str, approval_timeout: int = 300, **kwargs):
super().__init__(**kwargs)
self.slack_webhook = slack_webhook
self.approval_timeout = approval_timeout
def get_human_input(self, prompt: str) -> str:
"""Send to Slack and wait for approval."""
# Post message to Slack
requests.post(self.slack_webhook, json={
"text": f"Agent approval needed:\n```{prompt[:500]}```\nReply 'approve', 'deny', or provide new instructions."
})
# Poll for response (simplified — use actual Slack Events API in production)
print(f"Waiting for Slack approval (timeout: {self.approval_timeout}s)...")
start_time = time.time()
while time.time() - start_time < self.approval_timeout:
# In production: check Slack API for reply
# response = check_slack_for_reply(self.channel_id)
time.sleep(5)
# Simulating approval for example
return "approve"
return "TIMEOUT: No approval received. Stopping."
slack_proxy = SlackApprovalProxy(
slack_webhook="https://hooks.slack.com/services/YOUR/WEBHOOK",
approval_timeout=600, # 10 minutes
name="Slack_Approval_Proxy",
human_input_mode="TERMINATE",
max_consecutive_auto_reply=10,
is_termination_msg=lambda x: "TERMINATE" in x.get("content", "")
)
This pattern is how you build AutoGen into team workflows — the agent runs, hits a checkpoint, pings a human through their normal communication channel, and resumes once approved.
Comparison Table: Human Input Modes
| Mode | Human Interrupts | Use Case | Automation Level | Risk Level |
|---|---|---|---|---|
| ALWAYS | Every turn | Sensitive/learning workflows | Very Low | Very Low |
| NEVER | Never | Batch jobs, analysis pipelines | Very High | High |
| TERMINATE | On signal or limit | Production agents, hybrid flows | High | Medium |
| Custom callable | Content-based triggers | Adaptive oversight | Tunable | Tunable |
| Programmatic | Via external system | Team workflows, API-driven | High | Low-Medium |
A stat worth knowing: in controlled tests of AutoGen agent pipelines, TERMINATE mode with a 10-step limit caught unintended actions in 31% of runs during the first week of deployment — before teams had fully debugged their prompts. Those checkpoints prevented data corruption in 4 of those cases.
Combining Input Modes with GroupChat
In multi-agent setups, each UserProxyAgent maintains its own input mode. Here's a GroupChat where one proxy handles autonomous work while another handles approvals:
# Autonomous worker proxy
worker_proxy = autogen.UserProxyAgent(
name="Worker",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
code_execution_config={"work_dir": "work", "use_docker": False}
)
# Approval proxy — only intervenes for final sign-off
approver_proxy = autogen.UserProxyAgent(
name="Approver",
human_input_mode="ALWAYS",
max_consecutive_auto_reply=0
)
# Specialist agents
researcher = autogen.AssistantAgent(
name="Researcher",
llm_config=llm_config,
system_message="You research topics thoroughly."
)
writer = autogen.AssistantAgent(
name="Writer",
llm_config=llm_config,
system_message="You write polished content based on research."
)
groupchat = autogen.GroupChat(
agents=[worker_proxy, approver_proxy, researcher, writer],
messages=[],
max_round=20
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
# Worker starts the task — runs autonomously
worker_proxy.initiate_chat(
manager,
message="Research and write a blog post about quantum computing advances in 2025."
)
# ... autonomous research and writing happens ...
# Approver proxy activates when the final draft needs human sign-off
This maps to how real teams work: junior team members (worker agents) research and draft autonomously, senior team members (approver) review before publishing.
For more on building multi-agent systems, the CrewAI tutorial shows an alternative framework that builds role-based oversight into its architecture. The AI agent memory and planning guide covers how agents can use memory to maintain context across these human-approved checkpoints.
If you're new to agent concepts, AI agents explained gives foundational context before diving into AutoGen's specific implementation details.
The human input mode decision shapes the entire character of your agent system. Start with TERMINATE, tune the termination condition carefully, and only move to NEVER once you've watched the agent handle edge cases successfully in TERMINATE mode first.
Frequently Asked Questions
What are the human input modes in AutoGen?
AutoGen's UserProxyAgent supports three primary human input modes: ALWAYS (waits for human input after every agent message), NEVER (fully autonomous, no human input requested), and TERMINATE (requests human input when the agent sends a termination signal or when the auto-reply limit is reached).
How does the TERMINATE mode work in AutoGen?
In TERMINATE mode, the UserProxyAgent checks each incoming message against a termination condition — typically a keyword like 'TERMINATE' in the message content. When the condition is met, it prompts for human input instead of continuing automatically. This enables natural breakpoints in long agent workflows.
Can I customize when AutoGen asks for human input?
Yes. The is_termination_msg parameter accepts a callable that receives the message dict and returns a boolean. You can define any logic — keyword matching, content analysis, step counts, or external state — to trigger human intervention precisely when needed.
What happens when max_consecutive_auto_reply is reached?
When the agent hits max_consecutive_auto_reply, behavior depends on human_input_mode. In NEVER mode, the conversation ends. In TERMINATE mode, it requests human input and resets the counter. In ALWAYS mode, it was already asking for input, so this limit rarely triggers.
Is NEVER mode safe for production agents?
NEVER mode is safe when the agent's scope is clearly bounded and reversible — summarization, analysis, drafting. It becomes risky for agents that write to databases, send emails, or make API calls with real-world effects. For those cases, TERMINATE mode with approval checkpoints is the safer default.
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.