Skip to lesson

MicrosoftMicrosoft Certified: Multi-Agent AI Solutions Expert2026-102.4 Implement multi-agent orchestration

Choose where composition becomes workflow orchestration

Completed by the knowledge check

Learning objective

Implement LangChain agent/chain composition and compare its orchestration boundary with LangGraph and Agent Framework.

Core idea

For an architect choosing an orchestration framework, distinguish composing an operation from owning the lifecycle of a business process. A chain can transform input through several components. An agent can decide which tools to invoke. A workflow can coordinate these components with explicit state, branches, and resumption.

LangChain agents are built on LangGraph. The distinction is therefore the interface you choose to control, not an assertion that LangChain lacks an execution runtime. Use LangChain’s agent interface for a configured reasoning-and-tools loop, runnable composition for a defined data transformation, and an explicit workflow when the application needs to own routing and durable process state.

Microsoft Agent Framework provides another explicit workflow abstraction, with typed executors and message-routing edges. Choose the outer orchestration owner according to the process contract, then expose inner components through small input/output contracts. Avoid having two frameworks independently manage the same approval or recovery decision.

How it works

Compose chains through runnable interfaces

A RunnableSequence passes each component’s output to the next component as input. Construct it directly or use the | operator with runnable operands. Wrap an ordinary function with RunnableLambda when it needs to participate in this interface.

The sequence supports synchronous invocation with invoke(), asynchronous invocation with ainvoke(), and multi-input processing with batch() or abatch(). Batching applies each component’s batch method in sequence. It is a processing interface, not an instruction to turn each input into an autonomous agent.

This complete example makes an application-defined normalization contract visible:

from langchain_core.runnables import RunnableLambda

normalize = RunnableLambda(lambda text: {"text": text.strip()})
measure = RunnableLambda(
    lambda request: {
        "text": request["text"],
        "characters": len(request["text"]),
    }
)
chain = normalize | measure
print(chain.invoke("  claim  "))

The printed result is {'text': 'claim', 'characters': 5}. The second component consumes the dictionary produced by the first. If you change that shape, update the next component’s contract rather than assuming composition repairs it.

Use RunnableParallel when several components consume the same input concurrently and return a mapping of outputs. A dictionary of runnables inside a sequence also constructs this parallel composition. It differs from a sequence, where later components consume preceding outputs. See 2.4.2 for application quorum and partial-failure decisions; a parallel output mapping alone does not specify those decisions.

Preserve streaming intentionally

A RunnableSequence preserves component streaming when its components implement transform, which maps a streaming input to streaming output. A component without that method blocks streaming until it finishes; with multiple blocking components, streaming begins after the last one.

RunnableLambda does not support transform by default. A small formatting function placed after a model can therefore change time to first visible output, even when the model itself streams. Calling stream() or astream() on the sequence is not proof that every intermediate stage streams incrementally.

Choose whether your component produces a complete validated object or incremental content. A complete object can simplify validation, while incremental output can improve perceived latency. Measure the composed behavior instead of extrapolating from one model call. This is an interface decision, not a reason to discard validation.

Configure an agent loop

Use langchain.agents.create_agent with model=, tools=, and system_prompt= to configure the agent. The model and tools define what the loop can do; instructions describe its task. Use middleware= to extend the harness for concerns that belong inside that loop.

Invoke the agent with a state update containing messages, for example a user-role message. When conversation persistence is required, configure checkpointer= and pass configurable.thread_id in invocation configuration. The same thread ID continues that conversation; a different ID addresses another thread.

The documentation uses InMemorySaver in demonstrations, but RAM storage does not survive a process restart. See 2.4.12 for persistent checkpointers, conditional edges, and interrupts. An agent’s use of LangGraph makes those capabilities available through its configured runtime; it does not remove the need to choose and operate storage.

LangChain documents Subagents as a main agent coordinating specialists as tools: routing passes through the main agent. Its Router pattern classifies input, directs it to one or more specialist agents, and synthesizes their results. Its Custom workflow pattern uses LangGraph to combine deterministic and agentic execution, embedding other patterns as nodes. This is the documented transition from agent composition to explicit workflow ownership.

Keep tools narrow when composing agents. A specialist operation should receive the task data it needs and return a result the caller can validate. The calling agent’s ability to select a tool is separate from the business process’s decision that the result is acceptable.

Assign the outer process to one owner

LangGraph’s StateGraph exposes node registration, state reducers, conditional edges, and checkpointed resumption directly. Use it when the process requires explicit branches or cycles around several agents and deterministic operations. A LangChain agent can participate as a component inside that process; the graph owns the cross-component state and routing.

Agent Framework’s WorkflowBuilder connects executors and agents through typed message edges. AgentExecutor adapts an agent to the workflow protocol; ctx.send_message() advances processing and ctx.yield_output() exposes results. See 2.4.11 for that protocol. If you integrate a LangChain operation into such a workflow, use an application adapter that maps input and output explicitly, rather than treating a LangChain message as an Agent Framework message.

Microsoft Foundry hosted agents run bring-your-own-framework code, including LangGraph and Agent Framework. Hosting runtime and orchestration owner are separate choices; see 1.2.1 for hosting selection. The hosted-agent durable state store can hold framework checkpoints, but it is preview and available only to hosted agents; the application must write its state explicitly.

These are architecture choices, not claims about automatic cross-framework interoperability. Define which framework owns process checkpoints, which owns an inner conversation, and which reports the accepted result. A persisted inner conversation is not a substitute for an outer purchase or claims state machine.

Key distinctions and trade-offs

The following comparison identifies the control surface you implement:

InterfacePrimary composition unitDecision you control
LangChain runnable chainSequence or parallel mapping of runnablesInput/output transformation and execution mode
LangChain agentConfigured model-and-tools harnessAgent instructions, tools, middleware, and conversation persistence
LangGraph graphNodes over explicit stateConditional routing, reducers, checkpoints, and interrupts
Agent Framework graphTyped executors connected by edgesMessage routing, agent adaptation, and workflow output selection

A chain is compact for a known transformation. An agent handles tool selection inside a reasoning loop. An explicit graph adds process definitions and storage responsibilities, but makes cross-component decisions inspectable. The best choice is the smallest interface that expresses the required control.

Architecture / decision scenario

A support operation normalizes a request, asks a research agent for evidence, and sends sensitive adjustments for approval. Compose normalization and evidence formatting as runnable sequences. Configure the research specialist with create_agent, its permitted tools, and a conversation checkpointer where continuity is needed. Use the Subagents pattern when a main support agent invokes this specialist as a tool; use Router when input classification selects one or more specialists for synthesis.

If the service uses LangGraph, place the specialist behind a node and use explicit conditional edges for normal completion versus approval. The graph owns the adjustment state and interrupt resumption. The specialist’s conversation remains an inner concern.

If the surrounding service uses Agent Framework, implement an executor adapter for the research operation. It emits a typed evidence result to a validation executor; graph edges route the validated outcome. Agent Framework owns the outer approval lifecycle. Neither design places a business approval inside a formatting lambda or assumes that the specialist’s successful tool loop authorizes the adjustment.

Common exam traps

  • Saying that LangChain agents are independent of LangGraph.
  • Treating a runnable chain as an autonomous tool-selection loop.
  • Confusing parallel shared input with sequential output-to-input composition.
  • Assuming a streaming model makes every composed function stream.
  • Expecting InMemorySaver to survive worker restarts.
  • Letting inner-agent and outer-workflow recovery both own the same side effect.

What to remember

Use runnable composition for transformations and create_agent for an agent harness. Use LangGraph or Agent Framework explicitly when the application owns cross-component routing and resumption. Name one outer process owner and define adapters between component protocols.

Knowledge check

References