Lab 01 — AutoGen-Style Conversational GroupChat

Phase 23 · Lab 01 · Phase README · Warmup · Hitchhiker's

The problem

AutoGen (Microsoft) is named by name in the Citi Lead Agentic AI Engineer JD, and its coordination model is not a graph — it is a conversation. A team is a shared list of messages, a speaker-selection policy that decides who talks next, and a termination condition that decides when to stop. That is the entire control loop, and it is deliberately open-ended: nobody writes the path in advance; it emerges from selection and what the agents say. This lab builds that loop faithfully, with the LLM injected as a pure reply policy so the whole team is deterministic and you can assert the exact transcript.

What you build

PieceWhat it does
TextMessage(source, content)one turn on the shared conversation (AutoGen's message type)
Agent(name, policy)a participant whose reply is an injected policy(messages) -> str (the LLM stand-in)
MaxMessageTermination(n)stop at n total messages — the team-level step budget / backstop
TextMentionTermination(text)stop when a keyword (e.g. "TERMINATE") appears in the latest message
a | b, a & bcomposable termination: OR fires on either, AND fires only once both fire
RoundRobinGroupChatspeakers take turns in participant order, cycling
SelectorGroupChatan injected selector(messages, participants) -> name picks the next speaker
team.run(task)seed the task → check termination → select → reply → append → check → repeat

Termination conditions return a stop-reason string (or None), mirroring AutoGen's StopMessage — so when a team stops early you know which condition fired.

File map

FileRole
lab.pyyour implementation (fill the # TODOs — dataclasses and constructors are given)
solution.pyreference + a worked example: a round-robin writer/reviewer team and a selector router
test_lab.py22 tests: message fields, each termination condition + |/&, agent policy, round-robin order + wraparound, selector routing + validation, stop-reasons, determinism
requirements.txtpytest only

Run it

pytest test_lab.py -v                       # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v   # the reference (green)
python solution.py                          # the round-robin + selector worked example

Success criteria

  • MaxMessageTermination(n) fires at exactly n messages (counting the seed task); rejects n <= 0.
  • TextMentionTermination(text) fires only when the keyword is in the last message.
  • a | b fires as soon as either fires (first reason wins); a & b fires only once both hold.
  • RoundRobinGroupChat cycles speakers in order and wraps around past the last participant.
  • SelectorGroupChat routes per the injected selector and rejects a name that isn't a participant.
  • run(task) seeds the task as a "user" message, sets the right stop_reason, and is deterministic.
  • All 22 tests pass under both lab and solution.

How this maps to the real stack

  • Agent + injected policy is AutoGen's AssistantAgent (a real model client) and UserProxyAgent (a policy that reads human input or executes code). Injecting the policy is the production unit-test seam: the coordination logic must be correct independently of the model.
  • RoundRobinGroupChat / SelectorGroupChat are the real AutoGen autogen_agentchat.teams classes. The real SelectorGroupChat default selector is an LLM handed the conversation and roster; you inject a pure selector, which is exactly the selector_func override AutoGen exposes.
  • Termination conditions are autogen_agentchat.conditionsMaxMessageTermination, TextMentionTermination, TokenUsageTermination, TimeoutTermination, HandoffTermination, …, composed with |/&. Your stop_reason is their StopMessage.
  • team.run(task) returning a TaskResult is the real API (team.run / team.run_stream).

Limits of the miniature. Real conditions are stateful and async (fed the per-turn delta, remembering whether they've fired, needing reset()); the lab models them as pure functions of the whole transcript — faithful to the composition algebra, not the statefulness. Real agents call models over the network, stream tokens, and use tools inside each turn (Phase 02); the real runtime is the autogen-core actor/event system (WARMUP §3). The coordination shape — shared conversation

  • selection + composed termination — is exactly this.

Extensions (your own machine)

  • Stateful termination. Make the conditions carry state and consume per-turn deltas, add reset(), and implement & that accumulates across turns (so a condition that fired on message 2 still counts on message 6). Compare to the pure-function version.
  • More conditions. Add TokenUsageTermination (a token counter on the policy), TimeoutTermination (an injected clock, never time.time()), and SourceMatchTermination (stop after a named agent speaks).
  • Streaming. Turn run into a generator that yields each message as it's produced (run_stream), and print the live transcript.
  • Wire a real model. Swap one agent's policy for an actual model client and run the same team; confirm the coordination code is unchanged.

Interview / resume signal

"Built an AutoGen-style conversational team from scratch — a shared message conversation, round-robin and injected-selector speaker selection, and composable termination conditions (MaxMessage/TextMention, |/&) that return a stop-reason — and proved the coordination invariants (speaker order, which condition terminated, selector validation, determinism) in the transcript. The model is injected as a pure policy, which is exactly how you unit-test a real AutoGen team: the coordination is separable from the model."