Skip to lesson

MicrosoftMicrosoft Certified: Multi-Agent AI Solutions Expert2026-102.1 Design and implement advanced prompt engineering strategies in multi-agent solutions

Carry context without merging agent responsibilities

Completed by the knowledge check

Learning objective

Implement context-aware behavior across agent roles, handoffs, and shared workflow state.

Core idea

Context-aware behavior means that an agent responds to the request’s established facts, progress, and constraints while retaining its own responsibilities. A receiving agent should know what happened, what remains unresolved, and what it can do next. It should not acquire another role’s instructions or treat an earlier agent’s claim as proof that an action succeeded.

For an architect, the decision is which information travels through conversation history, which arrives as a bounded role input, and which lives in shared workflow state. These are different mechanisms. Microsoft Agent Framework provides agent sessions, handoff history synchronization, and workflow state, but none makes every stored value part of every model request automatically. State lifetime and ownership are covered in 0.7 and 1.1.10; this lesson focuses on how that state changes a role’s next response.

How it works

Select context for the receiving role

Give each role a stable behavioral contract and a request-specific view of the work. A claims reviewer might need the submitted claim, policy evidence, and unresolved discrepancies. A customer-facing writer might need the reviewed decision and its explanation, rather than the reviewer’s entire working conversation. The output contract belongs to 2.1.1; runtime prompt construction belongs to 2.1.2.

For Python workflow chains, AgentExecutor.context_mode controls the context consumed when an upstream AgentExecutorResponse reaches the executor’s from_response handler. The supported modes divide the choice precisely:

ModeContext receivedAppropriate decision
"full"Upstream input messages and response messagesPreserve request details that the specialist needs to interpret the result.
"last_agent"Upstream response messages onlyTransform a complete result, such as translating an approved explanation.
"custom"Messages selected by context_filterSupply a deliberately selected subset for the receiving role.

"full" is the default. "custom" requires a context_filter callable that takes and returns a list of messages. These are Python AgentExecutor controls for response chaining; they are not settings that universally change every orchestration’s history behavior.

For .NET agent executors, AIAgentHostOptions controls message handling. ReassignOtherAgentsAsUsers defaults to true: other agents’ messages take the User role, so the model treats them as task inputs. ForwardIncomingMessages also defaults to true: incoming messages travel downstream before the agent’s generated messages. These controls govern role assignment and forwarding, rather than selecting one of Python’s context modes.

Each agent executor maintains its own session by default; sharing requires an explicit session passed to compatible Python executors or a common session provider configured for .NET agents. For Python sequential workflows, SequentialBuilder(chain_only_agent_responses=True) sets context_mode="last_agent" for all agent participants. Choose that convenience only when every stage can work from the preceding response alone.

Select "last_agent" only when the previous response is sufficient. If the reviewer says “not eligible” without carrying the policy version or unresolved exception, a downstream writer cannot reconstruct those facts from an omitted input. Conversely, forwarding the entire conversation can expose irrelevant material and increase the opportunity for an upstream instruction to interfere with the receiving role. Filtering context is a design choice; enforcing tool permission remains the separate responsibility described in 1.1.7.

Preserve meaning across handoffs

Agent Framework handoff orchestration transfers task ownership to the receiving agent. It differs from agent-as-tool delegation, where the primary agent retains responsibility and receives a subtask result; see 1.1.3. Python HandoffBuilder.add_handoff() defines which agent can transfer control to which recipient. .with_start_agent() identifies the initial participant.

Handoff synchronization preserves user and agent conversation content but excludes tool and approval content; see 1.1.10 for the session and payload rules. Routing relationships control who takes over, not who receives synchronized messages. Restricting triage to an order specialist therefore does not exclude a refund specialist from conversation synchronization. Do not teach a receiving agent to infer a successful refund merely because a preceding assistant announced one. Supply a validated business result through an explicit workflow record or an authorized lookup. A handoff relationship transfers control to its recipient, while synchronization distributes conversation content across participants.

A handoff relationship transfers control to its recipient, while synchronization distributes conversation content across participantsSynchronized conversation content reaches every handoff participant: triage, the order specialist and the refund specialist. Only one edge transfers control, from triage to the order specialist, so restricting routing does not exclude the refund specialist from the synchronized messages.Handoff participantstransfers controlTriageOrder specialistRefund specialistSynchronizedconversationcontent

By default, a Python user response returns to the agent that requested it. enable_return_to_previous(False) instead returns it to the start agent for another routing decision. This changes which role interprets the next input; it does not erase the synchronized history. The page labels Python autonomous mode experimental. Its .with_autonomous_mode() continuation is a separate execution choice, not a requirement for preserving context.

Make shared state usable, not merely stored

Python WorkflowContext.set_state() and WorkflowContext.get_state() write and read workflow state available to downstream executors. Use consistent keys and make a deterministic executor validate a value before publishing it as an accepted result. For example, application-defined keys validated_claim and review_result can carry a request identifier, policy version, evidence references, and decision status. Those keys are your schema, not built-in Framework fields.

A state read does not itself explain the value to the model. The receiving executor must select the relevant fields and include them in the role’s input using the runtime context pattern in 2.1.2. Keep pending proposals separate from accepted outcomes so an agent can distinguish “review requested” from “review completed.” Define which executor updates each key, especially when several specialists contribute results.

.NET uses QueueStateUpdateAsync() and ReadStateAsync() with shared scopes and superstep visibility; see 1.2.5 for the scope and timing rules.

Key distinctions and trade-offs

Role instructions versus task context: Instructions define the specialist’s responsibilities. Shared history and state provide facts to interpret under those instructions. A preceding agent’s text does not become a trusted role definition; defensive instruction handling is covered in 2.1.3.

Routing versus disclosure: add_handoff() limits control transfer. Handoff synchronization distributes conversation content across participants. If two specialists must not receive the same data, choose separate workflows or explicitly filtered message paths instead of relying on handoff edges as a confidentiality control.

History versus accepted state: History preserves conversational meaning. A validated state record preserves the application’s decision. The two can disagree after a failed tool call or a corrected result, so define which record governs the next action.

Architecture / decision scenario

An insurer coordinates triage, review, and customer communication. Triage classifies a claim and hands it to review. Both roles need the customer’s corrections, so the team uses HandoffBuilder and configures add_handoff() relationships. It accepts that the handoff participants receive synchronized conversation messages and excludes a separate sensitive-fraud role from that workflow.

A deterministic executor validates the review output and publishes review_result through set_state(). The communication executor reads it through get_state(), then renders the accepted decision, policy version, and unresolved questions into the writer’s input. The writer does not treat a synchronized statement about a proposed payout as an executed payout.

A translation stage uses AgentExecutor(context_mode="last_agent") because the approved explanation is a complete translation input. If a customer disputes a policy detail, enable_return_to_previous(False) sends the next response back to triage for classification. In a .NET implementation, the accepted record uses a named shared scope and reaches other executors in the next superstep.

Common exam traps

  • Treating a handoff edge as a filter on synchronized conversation content.
  • Choosing "last_agent" when the downstream role still needs the original request.
  • Setting context_mode="custom" without supplying a context_filter.
  • Applying Python context_mode settings to .NET or to handoff synchronization.
  • Reading shared state without rendering the relevant fields into the agent input.
  • Assuming the start agent reevaluates every user response without enable_return_to_previous(False).

What to remember

Preserve enough meaning for the receiving role without transferring another role’s authority. Choose Python response-chain context explicitly, understand handoff synchronization, and render validated workflow state into the next agent input. Shared state needs consistent keys, defined writers, and language-specific visibility semantics.

Knowledge check

References