Skip to content

Conversation

@TaoChenOSU
Copy link
Contributor

@TaoChenOSU TaoChenOSU commented Jan 14, 2026

Motivation and Context

This completes #429

Description

  1. Add participant factory and orchestrator factory to group chat.
  2. Add participant factory and manager factory to Magentic.
  3. BREAKING: Rename with_standard_manager to with_manager since the API supports non-standard magentic manager implementations.
  4. BREAKING: Rename the participant_factories API to register_participant to align with the rest of the orchestrations.
  5. Tests and samples.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@TaoChenOSU TaoChenOSU self-assigned this Jan 14, 2026
Copilot AI review requested due to automatic review settings January 14, 2026 23:16
@TaoChenOSU TaoChenOSU added the workflows Related to Workflows in agent-framework label Jan 14, 2026
@github-actions github-actions bot changed the title Add factory pattern to GroupChat and Magentic Python: Add factory pattern to GroupChat and Magentic Jan 14, 2026
@TaoChenOSU TaoChenOSU moved this to In Review in Agent Framework Jan 14, 2026
@TaoChenOSU TaoChenOSU changed the title Python: Add factory pattern to GroupChat and Magentic [BREAKING] Python: Add factory pattern to GroupChat and Magentic Jan 14, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds factory pattern support to GroupChat and Magentic workflow builders, enabling reusable builder instances for creating multiple workflows with fresh agent instances. The PR also renames with_standard_manager to with_manager to better support both standard and custom manager implementations.

Changes:

  • Added register_participants() and factory support for participant creation in GroupChatBuilder and MagenticBuilder
  • Added manager_factory and agent_factory options to MagenticBuilder's with_manager() method
  • Added orchestrator_factory option to GroupChatBuilder's with_orchestrator() method
  • Renamed with_standard_manager() to with_manager() across all samples and tests
  • Consolidated orchestrator configuration methods in GroupChatBuilder into a single with_orchestrator() method

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
python/packages/core/agent_framework/_workflows/_magentic.py Added participant and manager factory support to MagenticBuilder; renamed with_standard_manager to with_manager
python/packages/core/agent_framework/_workflows/_group_chat.py Added participant and orchestrator factory support; consolidated with_agent_orchestrator and with_select_speaker_func into with_orchestrator
python/packages/core/agent_framework/_workflows/_handoff.py Renamed participant_factories() to register_participants() for API consistency
python/packages/core/agent_framework/_workflows/_sequential.py Updated error messages for consistency
python/packages/core/agent_framework/_workflows/_concurrent.py Updated error messages for consistency
python/packages/core/tests/workflow/test_magentic.py Added comprehensive factory tests and updated all method calls
python/packages/core/tests/workflow/test_group_chat.py Added comprehensive factory tests and updated all method calls
python/packages/core/tests/workflow/test_handoff.py Updated method calls to use register_participants()
python/packages/core/tests/workflow/test_workflow_kwargs.py Updated method calls to new API
python/samples/* Updated all sample code to use the new API

@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Jan 14, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework/_workflows
   _concurrent.py1902984%53, 62–63, 71–72, 91–92, 97, 102, 127, 132, 137–138, 144, 166, 176, 183, 350, 353, 381, 437, 449, 488, 490–491, 493, 498, 520, 524
   _group_chat.py2813886%55, 172, 333, 340, 367, 378–379, 385, 390, 406, 433–438, 440, 473–476, 478, 483–487, 598, 606, 624, 702, 708, 753, 773, 869, 888, 907, 917
   _handoff.py3845884%58, 110–111, 113, 142–143, 163–173, 175, 177, 179, 184, 284, 338, 363, 389, 397–398, 412, 461–462, 492, 539–541, 724, 731, 736, 823, 826, 835–838, 848, 853, 860, 866–869, 904, 909, 1106, 1109, 1117, 1135, 1142, 1217
   _magentic.py6109384%43, 48, 70–79, 84, 88–99, 264, 275, 279, 299, 360, 369, 371, 413, 430, 439–440, 442–444, 446, 457, 599, 601, 641, 689, 725–727, 729, 737–740, 744–747, 790, 817–820, 911, 917, 923, 962, 1000, 1029, 1046, 1057, 1111–1112, 1116–1118, 1142, 1163–1164, 1177, 1193, 1215, 1263–1264, 1302–1303, 1459, 1468, 1471, 1476, 1742, 1784, 1799, 1828
   _sequential.py1081288%74, 166, 184, 195, 201, 238, 240–241, 243, 248, 270, 274
TOTAL17094259384% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3085 208 💤 0 ❌ 0 🔥 1m 0s ⏱️

@TaoChenOSU
Copy link
Contributor Author

self._termination_condition: Callable[[list[ChatMessage]], bool | Awaitable[bool]] | None = None

def participant_factories(
def register_participants(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we align the HandoffBuilder.register_participants() signature with GroupChatBuilder and MagenticBuilder?

The HandoffBuilder uses Mapping[str, Callable[[], AgentProtocol]] (dict with explicit names) while the other two use Sequence[Callable[[], AgentProtocol | Executor]] (list)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by designed.

Agents in Handoff could potentially be configured to handoff only to a subset of other agents, thus requiring identifiers.

if self._orchestrator:
return self._orchestrator

if self._orchestrator_factory:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we inject ParticipantRegistry when orchestrator_factory returns a BaseGroupChatOrchestrator?

Currently, if the factory returns a ChatAgent, the builder wraps it with the resolved participants. But if it returns a BaseGroupChatOrchestrator, it's used as-is, meaning the builder's .participants() / .register_participants() are ignored for orchestration. Is this intentional, or should we align the behavior (or at least document the caveat)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my thinking, BaseGroupChatOrchestrator are for very advanced users and they really need to know what they are doing, including providing the right ParticipantRegistry. We can document the caveat.

This also keeps things simple. If we were to inject a ParticipantRegistry, what do we do if the custom orchestrator already configures a ParticipantRegistry? We cannot make the ParticipantRegistry an optional member (by adding a None type) because it's required for the orchestrator to function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think documenting it is fine, then, so we can point devs to it when there are questions. We can't assume that even "very advanced users" may know this caveat unless explicitly stated.

selection_func: GroupChatSelectionFunction | None = None,
orchestrator: BaseGroupChatOrchestrator | None = None,
orchestrator_factory: Callable[[], ChatAgent | BaseGroupChatOrchestrator] | None = None,
orchestrator_name: str | None = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we considered whether the orchestrator_name parameter in with_orchestrator() should apply to all orchestrator types, not just selection_func?

Currently orchestrator_name only affects the selection_func path. Should we allow naming the orchestrator for other configuration methods as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary. Agent based orchestrator will use the agent name. Custom orchestrator has its own name. It's documented in the comment that this param will only apply when selection_func is set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would a selection_func need a name? could you not just take the functionname?

@markwallace-microsoft markwallace-microsoft added the documentation Improvements or additions to documentation label Jan 15, 2026
sure all participants are synced and picking the next speaker according to the defined logic
until the termination conditions are met.
There are a few ways to configure the orchestrator:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should simplify this, why not do:

        agent: ChatAgent  | Callable[[], ChatAgent] | None = None,
        selection_func: GroupChatSelectionFunction | None = None,
        orchestrator: BaseGroupChatOrchestrator | Callable[[], BaseGroupChatOrchestrator] | None = None,
        orchestrator_name: str | None = None,

This leaves a lot less explaining to do. Maybe also have overloads of this method for the agent route, selection func, or orchestrator route.

self,
manager: MagenticManagerBase | None = None,
*,
manager_factory: Callable[[], MagenticManagerBase] | None = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing, I think taking a single param and providing the option for a concrete instance or a callable makes things simpler

workflow = (
GroupChatBuilder()
.with_agent_orchestrator(moderator)
.with_orchestrator(agent=moderator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we not make the verbs used in GroupChat and magentic aligned, so that either both use orchestrator or manager, for me Manager would be more understandable.

selection_func: GroupChatSelectionFunction | None = None,
orchestrator: BaseGroupChatOrchestrator | None = None,
orchestrator_factory: Callable[[], ChatAgent | BaseGroupChatOrchestrator] | None = None,
orchestrator_name: str | None = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would a selection_func need a name? could you not just take the functionname?

sure all participants are synced and picking the next speaker according to the defined logic
until the termination conditions are met.
There are a few ways to configure the orchestrator:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should simplify this, why not do:

        agent: ChatAgent  | Callable[[], ChatAgent] | None = None,
        selection_func: GroupChatSelectionFunction | None = None,
        orchestrator: BaseGroupChatOrchestrator | Callable[[], BaseGroupChatOrchestrator] | None = None,
        orchestrator_name: str | None = None,

This leaves a lot less explaining to do. Maybe also have overloads of this method for the agent route, selection func, or orchestrator route.

Alternatively you could look into single dispatch for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python workflows Related to Workflows in agent-framework

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

4 participants