MicrosoftMicrosoft Certified: Multi-Agent AI Solutions Expert2026-102.4 Implement multi-agent orchestration
Aggregate parallel results under explicit policy
Completed by the knowledge check
Learning objective
Implement parallel fan-out/fan-in with aggregation, quorum, and partial-failure policy
Core idea
Fan-out sends available input to independent workers. Fan-in collects their results. Aggregation decides what those results mean together. A quorum defines how much acceptable participation or agreement is sufficient, while partial-failure policy decides what happens when a branch cannot contribute.
Microsoft Agent Framework provides concurrent orchestration and custom aggregators. The documented default collection behavior is not your business policy for voting, mandatory checks, or degraded responses. Implement those decisions explicitly in a custom aggregator or collecting executor.
Keep three questions separate: did each branch return, did its result pass validation, and does the set of accepted results justify the final decision? Combining them into a count of successful calls produces answers that look complete while omitting required evidence.
How it works
Fan out independent work
Python ConcurrentBuilder(participants=[researcher, marketer, legal]).build() gives the input to participants that process it independently. Participants can be agents or executors. The .NET entry point for the agent pattern is AgentWorkflowBuilder.BuildConcurrent().
Use concurrent orchestration when branches need the same available input or separately prepared inputs. A worker that consumes another worker’s analysis is dependent, so place it after a join rather than among those independent participants. See 2.4.1 for prerequisite gates.
Concurrency reduces the time spent waiting on independent work, but it also makes multiple requests compete for capacity. Choose the worker set from the required perspectives rather than multiplying agents to make an answer appear more credible. Agreement between similar models using the same evidence is not independent corroboration.
For a custom graph, fan out with AddFanOutEdge in .NET or add_multi_selection_edge_group(..., selection_func=...) in Python. Fan in with AddFanInBarrierEdge in .NET or add_fan_in_edges in Python. These mechanisms define message flow. Your collecting executor defines whether the received messages satisfy the decision contract.
Replace collection with meaningful aggregation
The Python default aggregator produces one AgentResponse containing one assistant message per participant. It preserves separate contributions rather than proving consensus or producing a domain-approved answer.
Use ConcurrentBuilder(...).with_aggregator(callback).build() for a custom aggregation function. The documented Python callback consumes list[AgentExecutorResponse]. Each response includes executor_id and agent_response, letting the aggregator associate a contribution with its producer.
An aggregator can synthesize text, rank alternatives, or apply deterministic acceptance rules. Choose deterministic code for mandatory checks and vote counting. A synthesis model can explain accepted evidence, but it should not silently decide that a required safety review is optional because two other workers agree.
Define an application result envelope with a task identifier, branch identifier, status, evidence, and validated value. These fields are your contract, not built-in Agent Framework response fields. Retain the producer identity during normalization so duplicate or stale responses cannot become additional votes.
Specify the quorum before counting
A quorum must name its denominator and eligibility rules. Two acceptable results from three assigned independent branches is different from two matching answers among the branches that happened to return. Define whether abstentions, timeouts, invalid schemas, and conflicting evidence count against completion.
Also distinguish participation quorum from agreement quorum. Two complete reports satisfy a participation threshold even when they disagree. A decision requiring two matching recommendations needs an explicit comparison rule over normalized values, not a count of messages or a model’s statement that the team agrees.
Mandatory roles can override a numeric threshold. An application may require two of three commercial estimates while always requiring legal acceptance. That is a role-aware policy, not a majority of all participants. Put the rule in the custom aggregator or collecting executor and return its reason in the final payload.
The concurrent documentation specifies collection and custom aggregation; it does not define a quorum argument for these business rules. This lesson therefore implements quorum as application logic. Do not invent a builder option such as a majority threshold when the documented API does not supply one.
Represent partial failures deliberately
For expected branch failures, use custom executors to turn recoverable execution outcomes into typed status messages that the aggregator can inspect. An absent result, an exception escaping a worker, and a returned failed status are different situations. A custom aggregator receives returned responses; its existence alone does not establish that every worker exception becomes a response.
Your application policy might require all branches, accept a qualified subset, or return an incomplete decision. Define the deadline and failure classification in the worker wrapper. Ensure a branch that times out or fails validation has an explicit terminal status when the application intends to continue with partial results.
Do not treat omitted evidence as favorable evidence. A failed security analysis cannot be counted as a clean finding. If a mandatory branch is unavailable, the collector yields an incomplete result with the missing role rather than a positive recommendation assembled from the remaining prose.
For best-effort analysis, disclose the participating and missing branches in the result. Keep recovery bounded and preserve the task identity when retrying. See 0.10 for Python concurrency and result validation; this lesson owns the aggregate decision, not the language-level exception mechanics.
Expose branch progress separately
In Python, only the concurrent aggregator’s output surfaces as a terminal "output" event by default. Configure intermediate_output_from=[researcher, legal] to surface selected branch outputs as "intermediate" events as well.
Do not let the first branch update satisfy the caller’s request for an aggregate. The collector is the component that yields the terminal result through ctx.yield_output(...) in a custom executor workflow. Its payload must identify whether the policy accepted, degraded, or rejected the evidence set. Selected branches surface intermediate events, while only the collector yields the terminal result.
Key distinctions and trade-offs
Parallelism versus independence: Parallel execution is safe for work whose inputs exist. It does not remove data dependencies.
Collection versus consensus: The default AgentResponse collects contributions. A custom rule establishes agreement or acceptance.
Quorum versus mandatory role: A numeric threshold can tolerate missing optional workers. It cannot override a required specialist unless your policy explicitly permits that.
Waiting for all branches improves coverage but makes completion depend on slow or failing workers. Returning a subset improves availability but narrows what the answer can claim. Choose that trade-off before execution and encode it in the terminal result.
Architecture / decision scenario
A procurement assistant fans out one supplier dossier to financial, operational, and legal reviewers. The application requires two accepted commercial assessments and mandatory legal acceptance before recommending a contract.
Custom worker executors return normalized statuses with task and producer identifiers. The collector distinguishes completed reports from invalid responses and timeouts. A custom aggregator counts eligible financial and operational results, checks legal acceptance separately, and includes the exact missing or conflicting evidence in its terminal payload.
If the operational branch times out, the quorum is not met. If the legal branch fails, two positive commercial reports still cannot authorize a recommendation. If every role returns valid evidence but financial and operational recommendations conflict, participation is complete while agreement remains unresolved.
For an informational summary, a separate application policy can return a degraded report with missing roles disclosed. It cannot reuse that degraded result as contract approval. Intermediate worker events update the progress panel; only the collector’s output carries the aggregate decision.
Common exam traps
- Assuming the default aggregator performs voting or majority selection.
- Counting duplicate producer messages as independent votes.
- Redefining the quorum denominator after failures occur.
- Treating missing legal evidence as legal acceptance.
- Assuming a custom aggregator automatically catches worker exceptions.
- Returning an intermediate branch response as the completed aggregate.
What to remember
ConcurrentBuilder supplies parallel execution and collection; with_aggregator supplies the extension point for your policy. Validate contributions, preserve producer identity, and distinguish quorum from mandatory checks. Partial completion changes the permissible conclusion, not merely the formatting of the answer.
Knowledge check
Three reviewers return complete but conflicting recommendations. A Python ConcurrentBuilder uses its default aggregator. The application releases a decision only when two recommendations match. What must the team add to establish that decision?
An application requires two accepted commercial reports and legal acceptance before recommending a contract. Both commercial reports pass, but the legal branch times out. Which aggregate outcome follows that policy?
A quorum requires acceptable contributions from two of three assigned independent branches. The collector receives two valid messages from the same branch, a timeout status from the second branch, and nothing from the third. How should it count the evidence?
A best-effort workflow may return a partial report when an optional branch fails. The team adds a custom aggregator, but a worker exception escapes before that worker returns a response. Which change makes the failure available to the partial-result policy?
A concurrent workflow is configured with intermediate_output_from=[researcher]. The optional researcher’s finding streams to the caller before the mandatory reviewers finish. The caller needs the aggregate decision. How should the application treat that finding?
References
- Microsoft, Concurrent orchestration
- Microsoft, Edges
- Microsoft, Executors