Adding AutoGen human feedback to a multi-agent pipeline is what separates a toy demo from a deployable system. In this guide we wire a human checkpoint into a Microsoft AutoGen conversation using the built-in UserProxyAgent and a custom reply hook, so you can review or redirect agent output before it propagates.
Step 1: Install AutoGen and configure the LLM client
Pin a known-good version of the classic AutoGen package. The AgentChat rewrite changes APIs frequently; the pyautogen 0.2.x line is stable for the patterns below.
pip install pyautogen==0.2.32
AutoGen expects an OpenAI-style config_list. You can target any compliant endpoint. If you point AutoGen at an OpenAI-compatible gateway such as n4n.ai, the same config_list works and you get automatic fallback across providers when one is rate-limited, without rewriting your agent code.
from autogen import AssistantAgent, UserProxyAgent
config_list = [
{
"model": "gpt-4o-mini",
"base_url": "https://api.n4n.ai/v1",
"api_key": "YOUR_KEY",
}
]
llm_config = {"config_list": config_list, "cache_seed": None}
Keep cache_seed None during development so prompts aren’t memoized; set it to an integer in prod if you want deterministic caching.
Step 2: Define the assistant and human proxy agents
The assistant generates proposals. The UserProxyAgent represents the human (or a system boundary). For AutoGen human feedback, this agent is where the interception happens.
assistant = AssistantAgent(
name="planner",
llm_config=llm_config,
system_message="You are a concise planning agent. Propose one next step at a time.",
)
user_proxy = UserProxyAgent(
name="human_proxy",
human_input_mode="NEVER", # we will gate manually via register_reply
max_consecutive_auto_reply=0, # never auto-reply on behalf of human
code_execution_config=False, # no local code exec in this example
)
human_input_mode="NEVER" stops AutoGen from blocking on stdin by default. We’ll add a controlled block only when we want it.
Step 3: Force synchronous feedback with human_input_mode
The fastest way to get AutoGen human feedback in a script is to flip human_input_mode to "ALWAYS" on the proxy. Every assistant message then triggers a stdin prompt before the conversation continues.
user_proxy.human_input_mode = "ALWAYS"
user_proxy.initiate_chat(
assistant,
message="Draft a launch plan for a developer API.",
)
Run this and you’ll see the assistant’s proposal, then a > prompt. Type feedback, press enter, and the text is fed back as the human’s reply. This is sufficient for interactive CLIs but too blunt for production: it asks after every single turn.
Use "TERMINATE" if you only want to confirm the final summary. Use "NEVER" plus a custom hook (next step) when feedback must be conditional.
Step 4: Register a structured feedback function for conditional gating
For real systems, you want a checkpoint only when the assistant crosses a threshold—say, proposing an external action. AutoGen’s register_reply lets you insert a function that runs before the default human prompt.
def human_review(recipient, messages, sender, config):
# Only intercept messages from the planner
if sender is not assistant:
return False, None
last = messages[-1]["content"]
print(f"\n[PLANNER PROPOSAL]\n{last}\n")
verdict = input("Approve (y) / Edit (e) / Reject (r): ").strip().lower()
if verdict == "y":
return True, "Approved. Proceed to next step."
if verdict == "e":
correction = input("Enter your corrected instruction: ")
return True, correction
if verdict == "r":
return True, "REJECTED" # assistant will see this and should stop
return False, None # fall back to default behavior
user_proxy.register_reply(assistant, human_review, position=1)
user_proxy.human_input_mode = "NEVER" # we handle input ourselves
position=1 puts human_review ahead of AutoGen’s built-in reply logic. Returning (True, reply) consumes the turn; (False, None) defers. This pattern gives you typed, structured AutoGen human feedback instead of a raw text append.
You can extend human_review to read from a webhook, a Slack message, or a database poll instead of input(). The agent code doesn’t change.
Step 5: Run a multi-turn conversation and verify the loop
Wire the pieces together and start the chat. The human proxy initiates; the assistant replies; your hook fires.
if __name__ == "__main__":
user_proxy.initiate_chat(
assistant,
message="Propose step 1 of a 3-step plan to onboard a new LLM provider.",
clear_history=True,
)
Verifying success
A correct implementation shows three observable behaviors:
- The script prints
[PLANNER PROPOSAL]and pauses atApprove (y) / Edit (e) / Reject (r):. - Typing
eand supplying new text causes the assistant’s next message to reflect that correction (check the transcript). - Typing
rleads the assistant to yield or terminate within one turn, not loop indefinitely.
If you see the assistant auto-replying without a prompt, max_consecutive_auto_reply is likely non-zero or register_reply wasn’t positioned ahead of defaults. If you get a stdin error in a notebook, wrap input() in a try/except or use a GUI event loop.
Production notes
AutoGen human feedback is synchronous by default. In a service, block a worker thread, not the event loop. Persist messages to recover a half-finished review after a crash. When you route through a gateway that honors provider cache-control hints, set cache_seed consistently so repeated proposals with the same human edit hit cache and cut cost.
The UserProxyAgent is not magic—it is a plain agent that defaults to representing a person. Once you treat its reply path as a programmable boundary, you can build approval gates, red-team interrupts, or tiered sign-offs without forking the framework.