n4nAI

Build a planner, coder, and critic team with AutoGen

Build an autogen planner coder critic agent team with AutoGen group chat: hands-on tutorial splitting design, coding, and review into specialized agents.

n4n Team3 min read609 words

Audio narration

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

An autogen planner coder critic agent team separates the work of designing a solution, writing the code, and reviewing it into three specialized agents that talk to each other. This tutorial builds that team on AutoGen’s GroupChat, with runnable Python you can point at any OpenAI-compatible model endpoint.

Prerequisites

  • Python 3.10 or newer
  • autogen==0.2.32 (the stable 0.2 line; the 0.4 API is different and not used here)
  • An API key for an OpenAI-compatible LLM endpoint. If you want provider redundancy, point AutoGen’s config at an OpenAI-compatible gateway such as n4n.ai, which fronts 240+ models and fails over automatically when a provider is degraded.
  • Basic familiarity with Python dicts and async control flow

Install the framework before continuing:

pip install autogen==0.2.32

Step 1: Import and configure the LLM backend

AutoGen 0.2 uses a plain dict for LLM settings. We define one base config and derive per-agent configs so we can use a cheaper model for planning and a stronger one for implementation.

import autogen
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager

base_llm = {
    "seed": 42,
    "api_key": "YOUR_KEY",
    "base_url": "https://api.openai.com/v1",  # swap for your gateway
    "temperature": 0.2,
}

planner_llm = {**base_llm, "model": "gpt-4o-mini"}
coder_llm   = {**base_llm, "model": "gpt-4o"}
critic_llm  = {**base_llm, "model": "gpt-4o"}

A gateway like n4n.ai lets you address 240+ models via one endpoint, so you can change the "model" string without touching base_url or auth.

Step 2: Define the three agent roles

Each agent gets a tight system message. The planner never writes code. The coder writes only code blocks. The critic is terse and decisive.

planner = AssistantAgent(
    name="Planner",
    llm_config=planner_llm,
    system_message=(
        "You are a software architect. Given a task, output a concise plan with "
        "numbered steps. Do not write code. Stop when the plan is clear."
    ),
)

coder = AssistantAgent(
    name="Coder",
    llm_config=coder_llm,
    system_message=(
        "You are a Python implementer. Given a plan, write the minimal code to "
        "fulfill it. Output only code blocks with brief comments. No prose."
    ),
)

critic = AssistantAgent(
    name="Critic",
    llm_config=critic_llm,
    system_message=(
        "You are a senior reviewer. Given code, point out bugs, edge cases, and "
        "style issues. If the code is correct, reply with exactly 'LGTM'. Be specific."
    ),
)

We add a user proxy that never asks a human and terminates the chat when the critic approves.

user = UserProxyAgent(
    name="User",
    human_input_mode="NEVER",
    code_execution_config=False,
    default_auto_reply="",
    is_termination_msg=lambda m: "LGTM" in m.get("content", ""),
)

Step 3: Wire up the group chat

GroupChat manages the message list and speaker order. round_robin forces Planner → Coder → Critic → Planner… which keeps the review loop deterministic.

group = GroupChat(
    agents=[user, planner, coder, critic],
    messages=[],
    max_round=6,
    speaker_selection_method="round_robin",
)

manager = GroupChatManager(group, llm_config=base_llm)

Step 4: Run a simple task

Kick off the conversation with a concrete, small problem.

task = "Write a function that reverses a string without using slicing."
user.initiate_chat(manager, message=task)

Expected output (abridged):

User (to chat_manager):
Write a function that reverses a string without using slicing.

Planner (to chat_manager):
1. Accept a string input.
2. Initialize an empty list.
3. Iterate characters from last to first, append to list.
4. Join list into string and return.

Coder (to chat_manager):
def reverse_string(s: str) -> str:
    out = []
    for i in range(len(s)-1, -1, -1):
        out.append(s[i])
    return "".join(out)

Critic (to chat_manager):
LGTM

Because the critic’s message contains LGTM, the user proxy terminates and the process exits cleanly.

Step 5: Force a review cycle with a bug

Real code rarely passes first try. Change the coder prompt indirectly by giving a harder task, or simulate a bug by editing the coder’s system message to allow imperfect drafts. Here we just observe what happens when the coder makes an off-by-one error:

# Suppose Coder emitted:
# def reverse_string(s):
#     out = []
#     for i in range(len(s), 0, -1):
#         out.append(s[i])
#     return "".join(out)

The critic should respond:

Critic (to chat_manager):
Bug: range(len(s), 0, -1) starts at len(s), which is out of bounds, and stops before 0.
Fix: use range(len(s)-1, -1, -1).

With max_round=6 the chat continues: Planner stays quiet (plan already given), Coder speaks again with the fix, Critic reviews. If the fix is good, it replies LGTM and terminates.

Step 6: Use automatic speaker selection

Round robin is predictable but rigid. For open-ended tasks, let the manager LLM pick the next speaker:

group = GroupChat(
    agents=[planner, coder, critic, user],
    messages=[],
    max_round=9,
    speaker_selection_method="auto",
    allow_repeat_speaker=False,
)

auto costs one extra LLM call per transition. It lets the critic ask the planner for clarification instead of blindly approving. Keep allow_repeat_speaker=False to avoid the same agent rambling.

Step 7: Token metering and callbacks

AutoGen invokes registered callbacks after each completion. Use this to track spend locally:

def log_usage(usage):
    print(f"prompt={usage.get('prompt_tokens')} completion={usage.get('completion_tokens')}")

base_llm["callbacks"] = [log_usage]

If you routed through a gateway that does per-token metering, the same counts appear in your provider bill without extra instrumentation.

Step 8: Full runnable script

import autogen
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager

base_llm = {
    "seed": 42,
    "api_key": "YOUR_KEY",
    "base_url": "https://api.openai.com/v1",
    "temperature": 0.2,
}
planner_llm = {**base_llm, "model": "gpt-4o-mini"}
coder_llm   = {**base_llm, "model": "gpt-4o"}
critic_llm  = {**base_llm, "model": "gpt-4o"}

planner = AssistantAgent("Planner", llm_config=planner_llm,
    system_message="You are a software architect. Output numbered steps. No code.")
coder = AssistantAgent("Coder", llm_config=coder_llm,
    system_message="You are a Python implementer. Output only code blocks.")
critic = AssistantAgent("Critic", llm_config=critic_llm,
    system_message="You are a senior reviewer. Reply 'LGTM' if correct, else specifics.")

user = UserProxyAgent("User", human_input_mode="NEVER", code_execution_config=False,
    default_auto_reply="", is_termination_msg=lambda m: "LGTM" in m.get("content",""))

group = GroupChat([user, planner, coder, critic], messages=[], max_round=6,
    speaker_selection_method="round_robin")
manager = GroupChatManager(group, llm_config=base_llm)

user.initiate_chat(manager, message="Write a function that reverses a string without slicing.")

Practical notes

  • Role separation is prompt engineering. If the planner starts writing code, tighten its system message. If the critic writes fixes instead of reviews, tell it to never emit code blocks.
  • Latency is cumulative. Three agents means three sequential calls per cycle. Use the smallest capable model for the planner.
  • Termination is mandatory. Without is_termination_msg, round_robin will hit max_round and dump a partial state.
  • Persist messages. json.dump(group.messages, open("run.json","w")) gives you a replayable transcript for debugging agent behavior.

The autogen planner coder critic agent team pattern turns a single fuzzy prompt into a reviewable pipeline. It is not a replacement for CI or unit tests, but it catches obvious logic errors before the code reaches a human. Swap the roles for docs, test generation, or security review by changing system messages and keeping the same group chat scaffold.

Tagsautogenmulti-agentagent-rolesgroup-chat

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 multi-agent conversations & group chat posts →