n4nAI

AutoGen agent team tutorial: planner, coder, critic roles

Hands-on AutoGen tutorial: build a multi-agent team with planner, coder, and critic roles using group chat to plan, write, and review Python code.

n4n Team3 min read632 words

Audio narration

Coming soon — every post will get a voice note here.

Building an autogen agent team planner coder critic pipeline lets you delegate a coding task to a self-contained loop: one agent maps the problem, another writes the code, a third reviews it before execution. This tutorial walks through a runnable AutoGen setup that plans, implements, and critiques Python without human-in-the-loop, using a group chat to coordinate roles.

Prerequisites

  • Python 3.10 or newer
  • pyautogen 0.2.x (pip install pyautogen)
  • python-dotenv for env vars
  • An OpenAI-compatible endpoint and API key. Export OPENAI_API_KEY and optionally OPENAI_BASE_URL.
python -m venv .venv && source .venv/bin/activate
pip install pyautogen python-dotenv requests

You should be comfortable reading AutoGen GroupChat transcripts. No prior multi-agent experience required.

Configure the LLM client

AutoGen passes a plain dict to the OpenAI SDK. We read from environment to keep secrets out of source.

import os
from dotenv import load_dotenv

load_dotenv()

llm_config = {
    "model": os.getenv("MODEL", "gpt-4o-mini"),
    "api_key": os.getenv("OPENAI_API_KEY"),
    "base_url": os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
    "temperature": 0.2,
}

If you run this in production, point base_url at a gateway that handles provider fallback. n4n.ai exposes one OpenAI-compatible endpoint covering 240+ models and automatically reroutes when a provider is rate-limited, which keeps the agent team from stalling on a single vendor outage.

Define the autogen agent team planner coder critic roles

We instantiate three AssistantAgent instances and one UserProxyAgent that executes code locally.

Planner

The planner outputs a concrete step list and nothing else. Keeping it code-free prevents muddying the chat context.

from autogen import AssistantAgent

planner = AssistantAgent(
    name="Planner",
    system_message=(
        "You are a meticulous planner. Given a task, respond with a numbered "
        "step-by-step plan. Never write code. Keep steps under 5."
    ),
    llm_config=llm_config,
)

Coder

The coder consumes the plan and emits a single Python block. Narrow prompts improve adherence.

coder = AssistantAgent(
    name="Coder",
    system_message=(
        "You are a Python expert. Given a plan from the Planner, write clean, "
        "runnable Python in a single code block. Include error handling. "
        "Do not explain, just code."
    ),
    llm_config=llm_config,
)

Critic

The critic validates logic and security. It can approve or demand a rewrite.

critic = AssistantAgent(
    name="Critic",
    system_message=(
        "You review code for correctness, security, and edge cases. "
        "Reply 'APPROVED' or list specific fixes. Be concise."
    ),
    llm_config=llm_config,
)

User proxy

The proxy runs the code and feeds stdout back into the chat. Set human_input_mode="NEVER" for full autonomy.

from autogen import UserProxyAgent

user_proxy = UserProxyAgent(
    name="UserProxy",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=8,
    code_execution_config={"work_dir": "autogen_runs", "use_docker": False},
)

Wire the group chat

AutoGen’s GroupChat cycles speakers. We use auto selection so the manager picks the next agent based on context.

from autogen import GroupChat, GroupChatManager

group = GroupChat(
    agents=[user_proxy, planner, coder, critic],
    messages=[],
    max_round=12,
    speaker_selection_method="auto",
)

manager = GroupChatManager(groupchat=group, llm_config=llm_config)

To keep the autogen agent team planner coder critic flow strict, replace auto with a custom speaker_fn that enforces Planner → Coder → Critic → UserProxy order. For most tasks, auto works and lets the critic loop back if it rejects code.

Run a concrete task

We ask the team to fetch a public JSON API and extract a field.

task = (
    "Task: Fetch https://api.github.com/repos/microsoft/autogen and print "
    "the 'stargazers_count' value. Handle network errors."
)

user_proxy.initiate_chat(manager, message=task)

Expected output at checkpoints

After initiation, the Planner replies first:

1. Send GET request to the GitHub API URL.
2. Parse JSON response.
3. Extract 'stargazers_count'.
4. Print the value, catch RequestException.

The Coder then posts:

import requests

try:
    r = requests.get("https://api.github.com/repos/microsoft/autogen", timeout=10)
    r.raise_for_status()
    data = r.json()
    print("Stars:", data["stargazers_count"])
except requests.RequestException as e:
    print("Error:", e)

The Critic responds:

APPROVED

The UserProxy executes the block. Since requests is installed, you’ll see the star count:

Stars: 28000

(Exact number varies; GitHub updates live.) If the Critic had flagged missing timeout, the Coder would iterate. That loop is the core value of the autogen agent team planner coder critic pattern: the review gate catches naive mistakes before execution.

Strict speaker ordering

For deterministic pipelines, define a fixed handoff:

def speaker_fn(last_speaker, group):
    order = [planner, coder, critic, user_proxy]
    if last_speaker is None:
        return planner
    idx = order.index(last_speaker)
    return order[(idx + 1) % len(order)]

group = GroupChat(
    agents=[user_proxy, planner, coder, critic],
    messages=[],
    max_round=12,
    speaker_fn=speaker_fn,
)

This guarantees the planner never sees raw code before it has planned, and the critic always reviews before the proxy runs anything.

Handling execution failures

If the code raises ModuleNotFoundError or the API times out, the UserProxy returns the traceback as a message. The Critic sees it and can instruct the Coder to add a fallback or pip install note. Because max_consecutive_auto_reply is set, the loop will retry until approval or round limit.

Example failure message from proxy:

Exit code: 1
Traceback (most recent call last):
  File "solution.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

The Critic would reply: Add 'import subprocess; subprocess.run(["pip","install","requests"])' or document dependency. The Coder then amends.

Termination condition

Stop the chat when the task is verified done. Combine critic approval and successful stdout:

def is_done(msg):
    content = msg.get("content", "")
    return "APPROVED" in content and "Stars:" in content

group = GroupChat(
    agents=[user_proxy, planner, coder, critic],
    messages=[],
    max_round=12,
    speaker_fn=speaker_fn,
    termination_condition=is_done,
)

Why role separation matters

A single agent prompted to “plan, code, and review” will truncate the review step when context fills. Splitting the autogen agent team planner coder critic responsibilities forces explicit handoffs. Each agent has a narrow system prompt, which improves adherence and makes the transcript debuggable. You can swap the coder model for a cheaper one and keep a strong critic.

Production notes

Log the group chat messages to JSON for audit. Meter token usage via your endpoint’s per-token billing—if you use a gateway, per-token usage metering lets you attribute cost to each role by tagging the model field. Honor provider cache-control hints to cut repeat planning costs on similar tasks.

That is the full loop. You now have a runnable multi-agent coding crew that plans, implements, and self-reviews.

Tagsautogenagent-teamsmulti-agentroles

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All autogen agent teams for research & automation posts →