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
| Piece | What 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 & b | composable termination: OR fires on either, AND fires only once both fire |
RoundRobinGroupChat | speakers take turns in participant order, cycling |
SelectorGroupChat | an 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
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs — dataclasses and constructors are given) |
solution.py | reference + a worked example: a round-robin writer/reviewer team and a selector router |
test_lab.py | 22 tests: message fields, each termination condition + |/&, agent policy, round-robin order + wraparound, selector routing + validation, stop-reasons, determinism |
requirements.txt | pytest 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 exactlynmessages (counting the seed task); rejectsn <= 0. -
TextMentionTermination(text)fires only when the keyword is in the last message. -
a | bfires as soon as either fires (first reason wins);a & bfires only once both hold. -
RoundRobinGroupChatcycles speakers in order and wraps around past the last participant. -
SelectorGroupChatroutes per the injected selector and rejects a name that isn't a participant. -
run(task)seeds the task as a"user"message, sets the rightstop_reason, and is deterministic. -
All 22 tests pass under both
labandsolution.
How this maps to the real stack
Agent+ injected policy is AutoGen'sAssistantAgent(a real model client) andUserProxyAgent(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/SelectorGroupChatare the real AutoGenautogen_agentchat.teamsclasses. The realSelectorGroupChatdefault selector is an LLM handed the conversation and roster; you inject a pure selector, which is exactly theselector_funcoverride AutoGen exposes.- Termination conditions are
autogen_agentchat.conditions—MaxMessageTermination,TextMentionTermination,TokenUsageTermination,TimeoutTermination,HandoffTermination, …, composed with|/&. Yourstop_reasonis theirStopMessage. team.run(task)returning aTaskResultis 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, nevertime.time()), andSourceMatchTermination(stop after a named agent speaks). - Streaming. Turn
runinto 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."