Files
Tao Chen 72a6157c6a [BREAKING] Python: Enable instrumentation by default (#5865)
* Enable instrumentation by default

* Update samples

* Optimization when span is not recording

* Address Copilot comments

* Revert uv.lock

* Add warning

* Formatting

* Fix mypy

* Add disable_instrumentation() with sticky user-intent semantics

Add a public disable_instrumentation() entry point so users can explicitly opt
out of Agent Framework telemetry, with a sticky-disable flag that makes the
user's intent "leading" — no framework code path (foundry's
configure_azure_monitor, configure_otel_providers, enable_instrumentation,
enable_sensitive_telemetry, or direct OBSERVABILITY_SETTINGS.enable_*
writes) can re-enable instrumentation until the user explicitly clears the
disable with enable_instrumentation(force=True) /
enable_sensitive_telemetry(force=True).

Also addresses the two remaining unresolved review threads on the PR:
1. test_observability_settings_defaults_instrumentation_true pins the new
   "ENABLE_INSTRUMENTATION defaults to True when env unset" behavior.
2. test_enable_instrumentation_reads_env_sensitive_data restores coverage
   for the post-import load_dotenv() fallback path.

Implementation:
- ObservabilitySettings.enable_instrumentation / enable_sensitive_data become
  properties backed by _enable_*. While _user_disabled is True, the getters
  return False and the setters drop True writes (defense in depth so third-
  party writes can't subvert the disable).
- Public is_user_disabled read-only property lets integrations (e.g. foundry's
  configure_azure_monitor) cheaply check the disable state without poking at
  privates.
- enable_instrumentation() and enable_sensitive_telemetry() short-circuit with
  an info log when disabled; gain a force=True kwarg that clears the disable.
- configure_otel_providers() still creates providers / exporters / views so a
  later force-enable can use them, but logs an info message when called while
  disabled.
- Foundry's FoundryChatClient.configure_azure_monitor and
  FoundryAgent.configure_azure_monitor early-return when the user has
  disabled, so Azure Monitor's global providers aren't installed unnecessarily.

Tests: 11 new tests covering default-on, env re-read at call time, sticky
behavior against each re-enable surface (enable_instrumentation,
enable_sensitive_telemetry, configure_otel_providers, direct attribute
writes), force=True override, re-arming the disable, and the __all__ export.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: document disable_instrumentation() and force=True paths

Add a "Disabling instrumentation" section to the observability sample README
that walks through:

- The distinction between the ENABLE_INSTRUMENTATION env var (initial,
  non-sticky) and disable_instrumentation() (process-wide, sticky).
- Why the sticky semantics matter: framework integrations like
  FoundryChatClient.configure_azure_monitor() can call
  enable_instrumentation() as part of their setup, and the user's opt-out
  needs to win.
- All five surfaces guarded by the sticky disable (property reads, public
  enable functions, configure_otel_providers, direct attribute writes,
  is_user_disabled-aware integrations).
- The force=True escape hatch on both enable_instrumentation() and
  enable_sensitive_telemetry().
- How third-party integrations should consult OBSERVABILITY_SETTINGS.is_user_disabled.
- The limits of the disable (does not tear down existing providers /
  in-flight spans / third-party instrumentation, does not persist across
  processes).

Cross-links the new section from the ENABLE_INSTRUMENTATION row in the env
vars table.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: soften disable_instrumentation() overclaim about telemetry guarantees

Replace 'no telemetry will be emitted no matter what' (which is too strong,
since callers can still pass force=True or mutate private attributes) with
language framing the disable as a user-intent contract that library and
framework code is expected to honor: the framework actively short-circuits
the public enable paths, force=True and private-attribute writes are
acknowledged as out-of-contract escape hatches that integrations should
not use on the user's behalf.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: correct observability Dependencies section

- opentelemetry-sdk is no longer a hard dependency; it is lazily imported by
  create_resource(), create_metric_views(), and configure_otel_providers()
  with a clear ImportError when missing. Day-to-day instrumentation works
  with opentelemetry-api alone provided some other component configures the
  global OpenTelemetry providers (Azure Monitor, an APM agent, application
  bootstrap, etc.).
- opentelemetry-semantic-conventions-ai is no longer used anywhere in the
  source; remove it from the listed dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: replace stale observability migration guide with current PR's only relevant migration

The old guide documented the move away from setup_observability(otlp_endpoint=...)
which was an earlier-release API change unrelated to this PR and stale enough that
it's more confusing than helpful at this point. Replace it with a short note on the
single migration this PR introduces: callers of
enable_instrumentation(enable_sensitive_data=True) should switch to
enable_sensitive_telemetry(). Cross-link to the Disabling instrumentation section
for the rare 'force on without enabling sensitive data' use case where
enable_instrumentation() still applies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
72a6157c6a · 2026-05-20 11:52:08 +00:00
History
..
2026-03-31 15:20:35 +00:00
2026-03-31 15:20:35 +00:00

Workflows Getting Started Samples

Installation

Microsoft Agent Framework Workflows support ships with the core agent-framework or agent-framework-core package, so no extra installation step is required.

To install with visualization support:

pip install agent-framework[viz] --pre

To export visualization images you also need to install GraphViz.

Samples Overview

Foundational Concepts - Start Here

Begin with the _start-here folder in order. These three samples introduce the core ideas of executors, edges, agents in workflows, and streaming.

Sample File Concepts
Executors and Edges _start-here/step1_executors_and_edges.py Minimal workflow with basic executors and edges
Agents in a Workflow _start-here/step2_agents_in_a_workflow.py Introduces adding Agents as nodes; calling agents inside a workflow
Streaming (Basics) _start-here/step3_streaming.py Extends workflows with event streaming

Once comfortable with these, explore the rest of the samples below.


Samples Overview (by directory)

functional

Write workflows as plain Python async functions — no graph concepts, no executor classes, no edges. Use native control flow (if/else, loops, asyncio.gather) for branching and parallelism.

Sample File Concepts
Basic Pipeline functional/basic_pipeline.py Sequential steps as plain async functions
Basic Streaming Pipeline functional/basic_streaming_pipeline.py Stream workflow events in real time with run(stream=True)
Parallel Pipeline functional/parallel_pipeline.py Fan-out/fan-in with asyncio.gather
Steps and Checkpointing functional/steps_and_checkpointing.py @step decorator for per-step checkpointing and observability
Human-in-the-Loop Review functional/hitl_review.py HITL with ctx.request_info() and replay
Agent Integration functional/agent_integration.py Calling agents inside workflow steps
Naive Group Chat functional/naive_group_chat.py Simple round-robin group chat as a plain loop

agents

Sample File Concepts
Azure Chat Agents (Streaming) agents/azure_chat_agents_streaming.py Add Azure Chat agents as edges and handle streaming events
Azure AI Agents (Streaming) agents/azure_ai_agents_streaming.py Add Azure AI agents as edges and handle streaming events
Azure AI Agents (Shared Thread) agents/azure_ai_agents_with_shared_session.py Share a common message session between multiple Azure AI agents in a workflow
Custom Agent Executors agents/custom_agent_executors.py Create executors to handle agent run methods
Workflow as Agent (Reflection Pattern) agents/workflow_as_agent_reflection_pattern.py Wrap a workflow so it can behave like an agent (reflection pattern)
Workflow as Agent + HITL agents/workflow_as_agent_human_in_the_loop.py Extend workflow-as-agent with human-in-the-loop capability
Workflow as Agent with Session agents/workflow_as_agent_with_session.py Use AgentSession to maintain conversation history across workflow-as-agent invocations
Workflow as Agent kwargs agents/workflow_as_agent_kwargs.py Pass custom context (data, user tokens) via kwargs through workflow.as_agent() to @tool tools

checkpoint

Sample File Concepts
Checkpoint & Resume checkpoint/checkpoint_with_resume.py Create checkpoints, inspect them, and resume execution
Checkpoint & HITL Resume checkpoint/checkpoint_with_human_in_the_loop.py Combine checkpointing with human approvals and resume pending HITL requests
Checkpointed Sub-Workflow checkpoint/sub_workflow_checkpoint.py Save and resume a sub-workflow that pauses for human approval
Handoff + Tool Approval Resume orchestrations/handoff_with_tool_approval_checkpoint_resume.py Handoff workflow that captures tool-call approvals in checkpoints and resumes with human decisions
Workflow as Agent Checkpoint checkpoint/workflow_as_agent_checkpoint.py Enable checkpointing when using workflow.as_agent() with checkpoint_storage parameter
Cosmos DB Checkpoint Storage checkpoint/cosmos_workflow_checkpointing.py Use CosmosCheckpointStorage for durable workflow checkpointing backed by Azure Cosmos DB NoSQL
Cosmos DB + Foundry Checkpoint checkpoint/cosmos_workflow_checkpointing_foundry.py Multi-agent workflow using FoundryChatClient with CosmosCheckpointStorage for durable pause/resume

composition

Sample File Concepts
Sub-Workflow (Basics) composition/sub_workflow_basics.py Wrap a workflow as an executor and orchestrate sub-workflows
Sub-Workflow: Request Interception composition/sub_workflow_request_interception.py Intercept and forward sub-workflow requests using @handler for SubWorkflowRequestMessage
Sub-Workflow: Parallel Requests composition/sub_workflow_parallel_requests.py Multiple specialized interceptors handling different request types from same sub-workflow
Sub-Workflow: kwargs Propagation composition/sub_workflow_kwargs.py Pass custom context (user tokens, config) from parent workflow through to sub-workflow agents

control-flow

Sample File Concepts
Sequential Executors control-flow/sequential_executors.py Sequential workflow with explicit executor setup
Sequential (Streaming) control-flow/sequential_streaming.py Stream events from a simple sequential run
Edge Condition control-flow/edge_condition.py Conditional routing based on agent classification
Switch-Case Edge Group control-flow/switch_case_edge_group.py Switch-case branching using classifier outputs
Multi-Selection Edge Group control-flow/multi_selection_edge_group.py Select one or many targets dynamically (subset fan-out)
Simple Loop control-flow/simple_loop.py Feedback loop where an agent judges ABOVE/BELOW/MATCHED
Workflow Cancellation control-flow/workflow_cancellation.py Cancel a running workflow using asyncio tasks
Workflow and Intermediate Outputs control-flow/intermediate_vs_terminal_outputs.py Select Workflow Output and Intermediate Output executors; hide unselected yields; map Intermediate Output events to text_reasoning content via as_agent

human-in-the-loop

Sample File Concepts
Human-In-The-Loop (Guessing Game) human-in-the-loop/guessing_game_with_human_input.py Interactive request/response prompts with a human via ctx.request_info()
Agents with Approval Requests in Workflows human-in-the-loop/agents_with_approval_requests.py Agents that create approval requests during workflow execution and wait for human approval to proceed
Agents with Declaration-Only Tools human-in-the-loop/agents_with_declaration_only_tools.py Workflow pauses when agent calls a client-side tool (func=None), caller supplies the result

Builder-oriented request-info samples are maintained in the orchestration sample set (sequential, concurrent, and group-chat builder variants).

tool-approval

Builder-based tool approval samples are maintained in the orchestration sample set.

observability

Sample File Concepts
Executor I/O Observation observability/executor_io_observation.py Observe executor input/output data via executor_invoked events (type='executor_invoked') and executor_completed events (type='executor_completed') without modifying executor code

For additional observability samples in Agent Framework, see the observability concept samples. The workflow observability sample demonstrates integrating observability into workflows.

orchestration

Orchestration-focused samples (Sequential, Concurrent, Handoff, GroupChat, Magentic), including builder-based workflow.as_agent(...) variants, are documented in the orchestrations directory.

output selection

Workflow Output selection controls which ctx.yield_output(...) calls are visible to callers as type='output' events and through WorkflowRunResult.get_outputs(). The core rule is that output_from is an allow-list for Workflow Output, not a routing rule for every other executor output. Unselected executor payloads are hidden unless intermediate_output_from explicitly selects them as Intermediate Output.

Use output_from and intermediate_output_from as the canonical API:

Selection Workflow Output Intermediate Output Hidden payloads
Omit both selections Every executor yield_output; emits a deprecation warning None None
output_from="all" Every executor yield_output; no warning None None
output_from=[answerer] Only answerer None All other executor payloads
output_from=[answerer], intermediate_output_from="all_other" Only answerer Every output-capable executor not selected by output_from None
intermediate_output_from="all_other" None Every output-capable executor None
output_from=[], intermediate_output_from="all_other" None Every output-capable executor None
output_from=[answerer], intermediate_output_from=[planner, researcher] Only answerer planner and researcher Any other executor payloads

Invalid selections fail at construction or build time:

Invalid selection Why it fails
output_from="all_other" "all_other" is only valid for intermediate_output_from
intermediate_output_from="all" "all" is only valid for output_from
The same executor in both selections One payload cannot be both Workflow Output and Intermediate Output
Duplicate executor selections Duplicates are treated as configuration errors
Unknown executor selections Typos and missing participants are rejected
output_from=[], intermediate_output_from=[] Both explicit selections are empty

Compatibility aliases such as output_executors emit deprecation warnings where supported. New samples and applications should use output_from and intermediate_output_from.

When a workflow is wrapped with workflow.as_agent(), Workflow Output becomes normal agent text content. Intermediate Output becomes text_reasoning content, so AgentResponse.text remains focused on the caller-facing answer while callers can still inspect progress or supporting work from the response messages.

parallelism

Sample File Concepts
Concurrent (Fan-out/Fan-in) parallelism/fan_out_fan_in_edges.py Dispatch to multiple executors and aggregate results
Aggregate Results of Different Types parallelism/aggregate_results_of_different_types.py Handle results of different types from multiple concurrent executors
Map-Reduce with Visualization parallelism/map_reduce_and_visualization.py Fan-out/fan-in pattern with diagram export

state-management

Sample File Concepts
State with Agents state-management/state_with_agents.py Store in state once and later reuse across agents
Workflow Kwargs - Global Context state-management/workflow_kwargs_global.py Pass custom context (data, user tokens) via kwargs to @tool tools in all agents
Workflow Kwargs - Per Agent state-management/workflow_kwargs_per_agent.py Pass custom context (data, user tokens) via kwargs to @tool tools in individual agents

visualization

Sample File Concepts
Concurrent with Visualization visualization/concurrent_with_visualization.py Fan-out/fan-in workflow with diagram export

declarative

YAML-based declarative workflows allow you to define multi-agent orchestration patterns without writing Python code. See the declarative workflows README for more details on YAML workflow syntax and available actions.

Sample File Concepts
Agent to Function Tool declarative/agent_to_function_tool/ Chain agent output to InvokeFunctionTool actions
Conditional Workflow declarative/conditional_workflow/ Nested conditional branching based on user input
Customer Support declarative/customer_support/ Multi-agent customer support with routing
Deep Research declarative/deep_research/ Research workflow with planning, searching, and synthesis
Function Tools declarative/function_tools/ Invoking Python functions from declarative workflows
Human-in-Loop declarative/human_in_loop/ Interactive workflows that request user input
Invoke Function Tool declarative/invoke_function_tool/ Call registered Python functions with InvokeFunctionTool
Marketing declarative/marketing/ Marketing content generation workflow
Simple Workflow declarative/simple_workflow/ Basic workflow with variable setting, conditionals, and loops
Student Teacher declarative/student_teacher/ Student-teacher interaction pattern

resources

Notes

  • Agent-based samples use provider SDKs (Azure/OpenAI, etc.). Ensure credentials are configured, or adapt agents accordingly.

Sequential orchestration uses a few small adapter nodes for plumbing:

  • "input-conversation" normalizes input to list[Message]
  • "to-conversation:" converts agent responses into the shared conversation
  • "complete" publishes the Workflow Output event (type='output') These may appear in event streams (executor_invoked/executor_completed). They're analogous to concurrents dispatcher and aggregator and can be ignored if you only care about agent activity.

Why FoundryChatClient?

Workflow and orchestration samples use FoundryChatClient because they create agents locally and do not need server-managed agent resources. This lightweight, project-backed chat client is a good fit for orchestration patterns such as Sequential, Concurrent, Handoff, GroupChat, and Magentic.

If you need persistent server-side agent resources, use the hosted-agent flows rather than these workflow samples.

Environment Variables

Workflow samples that use FoundryChatClient expect:

  • FOUNDRY_PROJECT_ENDPOINT (Azure AI Foundry Agent Service (V2) project endpoint)
  • FOUNDRY_MODEL (model deployment name)

These values are passed directly into the client constructor via os.getenv() in sample code.