AutoGen Tutorial (Python): debugging agent loops for advanced developers

By Cyprian AaronsUpdated 2026-04-21
autogendebugging-agent-loops-for-advanced-developerspython

This tutorial shows you how to instrument an AutoGen agent loop so you can see why it’s repeating, stalling, or never reaching termination. You need this when a multi-agent workflow looks “fine” at the prompt level but fails in production because the loop is hiding bad tool calls, missing termination conditions, or runaway message growth.

What You'll Need

  • Python 3.10+
  • autogen-agentchat
  • autogen-ext
  • An OpenAI-compatible API key
  • A model that supports chat completions
  • Basic familiarity with AssistantAgent, UserProxyAgent, and group chat patterns
  • A local shell where you can set environment variables

Install the packages:

pip install autogen-agentchat autogen-ext openai

Set your API key:

export OPENAI_API_KEY="your-key-here"

Step-by-Step

  1. Start with a minimal agent loop that can actually fail in a visible way. The goal is not to build the full app first; it’s to reproduce the loop behavior with as little surface area as possible.
import asyncio
import os

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient

async def main():
    client = OpenAIChatCompletionClient(
        model="gpt-4o-mini",
        api_key=os.environ["OPENAI_API_KEY"],
    )

    agent = AssistantAgent(
        name="debugger",
        model_client=client,
        system_message="You are a concise assistant."
    )

    result = await agent.on_messages(
        [TextMessage(content="Say hello in one sentence.", source="user")],
        cancellation_token=None,
    )
    print(result.chat_message.content)

asyncio.run(main())
  1. Add explicit tracing around every turn so you can inspect what the agent received and returned. In practice, most “agent loop bugs” are just message-history bugs, so print the inputs and outputs before you change anything else.
import asyncio
import os

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient

async def main():
    client = OpenAIChatCompletionClient(
        model="gpt-4o-mini",
        api_key=os.environ["OPENAI_API_KEY"],
    )

    agent = AssistantAgent(name="debugger", model_client=client)

    messages = [
        TextMessage(content="Debug this loop behavior.", source="user")
    ]

    for i in range(3):
        print(f"\n--- TURN {i+1} INPUT ---")
        for m in messages:
            print(type(m).__name__, m.source, m.content)

        result = await agent.on_messages(messages, cancellation_token=None)
        reply = result.chat_message

        print(f"--- TURN {i+1} OUTPUT ---")
        print(reply.source, reply.content)

        messages.append(reply)

asyncio.run(main())
  1. Reproduce termination logic explicitly instead of assuming the framework will stop for you. If your agents keep talking forever, you need a deterministic rule for when to exit the loop, and that rule should be visible in code.
import asyncio
import os

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient

def should_stop(message: str) -> bool:
    return "DONE" in message.upper()

async def main():
    client = OpenAIChatCompletionClient(
        model="gpt-4o-mini",
        api_key=os.environ["OPENAI_API_KEY"],
    )

    agent = AssistantAgent(
        name="worker",
        model_client=client,
        system_message="End every completed answer with DONE."
    )

    messages = [TextMessage(content="Give me one debugging tip.", source="user")]

    for _ in range(5):
      result = await agent.on_messages(messages, cancellation_token=None)
      reply = result.chat_message
      print(reply.content)
      messages.append(reply)
      if should_stop(reply.content):
          break

asyncio.run(main())
  1. Inspect tool-call and response drift by forcing a structured output contract. When an agent loop starts failing after a few turns, it’s often because the model stopped following your implicit format and your downstream parser is silently breaking.
import asyncio
import json
import os

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient

SYSTEM = """Return JSON only:
{"status":"ok|stop","reason":"..."}
If finished, use status=stop."""

async def main():
    client = OpenAIChatCompletionClient(
        model="gpt-4o-mini",
        api_key=os.environ["OPENAI_API_KEY"],
    )

    agent = AssistantAgent(name="json_agent", model_client=client, system_message=SYSTEM)
    messages = [TextMessage(content="Tell me when you're done.", source="user")]

    result = await agent.on_messages(messages, cancellation_token=None)
    raw = result.chat_message.content
    print(raw)
    parsed = json.loads(raw)
    print(parsed["status"], parsed["reason"])

asyncio.run(main())
  1. Put guardrails around recursion depth and repeated content before it hits production. A lot of bad loops are not infinite; they’re just expensive enough to look infinite because the same prompt keeps re-entering through orchestration code.
from collections import deque

class LoopGuard:
    def __init__(self, max_turns=6):
        self.max_turns = max_turns
        self.seen = deque(maxlen=3)

    def hit(self, content: str) -> bool:
        self.seen.append(content.strip().lower())
        if len(self.seen) < 3:
            return False
        return len(set(self.seen)) == 1

guard = LoopGuard(max_turns=6)

sample_outputs = [
    "Working on it.",
    "Working on it.",
    "Working on it.",
]

for idx, output in enumerate(sample_outputs, start=1):
    if guard.hit(output):
      print(f"Loop detected at turn {idx}: repeated output")
      break

Testing It

Run each snippet independently and confirm you get a visible assistant response on every turn. Then intentionally break your termination condition or remove the DONE token and verify that your guard stops the loop instead of letting it run indefinitely.

If you’re debugging a real multi-agent workflow, log three things per turn: input messages, raw model output, and stop condition evaluation. That gives you enough signal to distinguish between prompt issues, parser issues, and orchestration issues without guessing.

Next Steps

  • Add AutoGen event logging around tool calls and handoffs so you can trace which agent triggered each transition.
  • Wrap your loop in a timeout plus retry policy so stalled turns fail fast instead of burning tokens.
  • Move from ad hoc prints to structured logs with turn IDs, conversation IDs, and stop reasons for production debugging

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides