הערה
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות להיכנס או לשנות מדריכי כתובות.
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות לשנות מדריכי כתובות.
The Agent 365 exporter requires a token resolver to authenticate when exporting telemetry. This guide covers setup for agents built with the Microsoft 365 Agents SDK, covering both Agent 365-enabled agents and custom engine agents across .NET, Python, and Node.js.
For distro installation, general configuration, and non-Agent SDK scenarios, see Microsoft OpenTelemetry Distro.
Overview
There are four authentication scenarios, depending on your agent type and how it acquires tokens. Token acquisition can use On-Behalf-Of flow (OBO) or Service-to-Service (S2S). Choose the scenario that matches your setup:
| Scenario | Description |
|---|---|
| Agent 365-enabled using OBO | The distro's built-in AgenticTokenCache handles token acquisition automatically. No custom resolver needed. This is the recommended approach for Agent 365-enabled agents. |
| Agent 365-enabled using S2S | The agent acquires a token by using the agentic identity chain (getAgenticApplicationToken + Microsoft Authentication Libraries (MSAL)). Requires a custom TokenResolver. Use this approach when OBO isn't available or you need app-only tokens. |
| Custom engine using OBO | The agent gets a user token via Azure Bot OAuth, scoped to the observability API. Requires a custom TokenResolver and an Azure Bot OAuth connection. |
| Custom engine using S2S | The agent acquires an app-only token using client credentials. Requires a custom TokenResolver. The app registration must be a standard (non-agentic) app. |
Agent 365-enabled using OBO
Agent 365-enabled agents receive requests with agentic identity (agenticAppId, agenticUserId) from the Agent 365 platform. With OBO, the distro's built-in AgenticTokenCache handles token acquisition automatically : no custom token resolver is needed.
Prerequisites
- Entra app registration : A service principal (app registration) with Client ID, Client Secret, and Tenant ID
- Delegated API permissions : Add
Agent365.Observability.OtelWrite(Delegated), grant admin consent. For detailed steps, see Grant the permission.
Setup
On each turn, your agent calls the RegisterObservability function with the turn context. The built-in cache uses the user's delegated token from the AgenticUserAuthorization handler to perform an OBO exchange, acquiring a token scoped to Agent365.Observability.OtelWrite.
For full setup instructions including packages, configuration, and code examples, see Agentic token cache with Agent Framework apps.
Agent 365-enabled using S2S
Agent 365-enabled agents can also use S2S (service-to-service) authentication instead of OBO. The agent acquires a token using its own service principal identity via a two-step agentic identity chain:
getAgenticApplicationToken(tenantId, agentId): client credentials + Federated Managed Identity (FMI) path- MSAL
acquireTokenForClientwith the app token asclientAssertionand scopeapi://9b975845-388f-4429-889e-eab1ef63949c/.default
Note
Federated Managed Identity (FMI) is an architecture where a managed identity participates in workload identity federation via federated identity credentials, enabling token exchange and secretless authentication based on trust relationships between identities.
You must provide a custom TokenResolver and set UseS2SEndpoint = true.
Prerequisites
Entra app registration : A service principal (app registration) with Client ID, Client Secret, and Tenant ID
Application API permissions : Add
Agent365.Observability.OtelWrite(Application), grant admin consentAgent365.Observability.OtelWriteapp role : The agent's service principal must have theOtelWriterole assigned on the Agent365 Observability resource. Use the Agent 365 CLI:a365 setup permissions bot --config-dir "<path-to-config-dir>"Note
Role propagation might take a few minutes. Initial 401 or 403 errors from the export endpoint are expected during this period.
Step 1: Environment configuration
The following code examples show how to set the required connection, tenant, client credential, and observability exporter environment settings before enabling the custom S2S token flow.
No AgenticUserAuthorization handler needed. S2S uses the manual agentic identity chain (get_agentic_application_token + MSAL acquire_token_for_client) to get a token scoped to the observability resource.
CONNECTIONSMAP__0__SERVICEURL=*
CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>
ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true
Step 2: Configure the distro with custom token resolver
The following examples show how to enable Agent 365 exporting and register a custom TokenResolver so the exporter can retrieve S2S tokens for each agent and tenant.
from microsoft.opentelemetry import use_microsoft_opentelemetry
_token_cache: dict[str, str] = {}
def token_resolver(agent_id: str, tenant_id: str) -> str | None:
return _token_cache.get(f"{agent_id}:{tenant_id}")
use_microsoft_opentelemetry(
enable_a365=True,
a365_token_resolver=token_resolver,
a365_use_s2s_endpoint=True,
a365_enable_observability_exporter=True,
)
Step 3: Acquire and cache the S2S token
On each incoming message, acquire the S2S token through the agentic identity chain and cache it for the resolver.
import asyncio
from msal import ConfidentialClientApplication
from microsoft.opentelemetry.a365.core import BaggageBuilder, InvokeAgentScope, InvokeAgentScopeDetails, Request
OBSERVABILITY_S2S_SCOPE = "api://9b975845-388f-4429-889e-eab1ef63949c/.default"
async def get_agentic_s2s_token(connection, tenant_id: str, agent_id: str) -> str:
# Step 1: Get agentic application token (client_credentials + fmi_path)
app_token = await connection.get_agentic_application_token(tenant_id, agent_id)
if not app_token:
raise ValueError(f"Failed to get agentic app token for agent {agent_id}")
# Step 2: Exchange for observability-scoped token
cca = ConfidentialClientApplication(
client_id=agent_id,
authority=f"https://login.microsoftonline.com/{tenant_id}",
client_credential={"client_assertion": app_token},
)
result = await asyncio.to_thread(
lambda: cca.acquire_token_for_client(scopes=[OBSERVABILITY_S2S_SCOPE])
)
if not result or "access_token" not in result:
raise ValueError(f"Token acquisition failed: {result}")
return result["access_token"]
# In your message handler : use SDK helpers to get agent/tenant from the activity:
@AGENT_APP.activity("message")
async def on_message(context: TurnContext, _state: TurnState):
# get_agentic_instance_id reads from recipient (SDK convention)
agent_id = context.activity.get_agentic_instance_id()
tenant_id = context.activity.get_agentic_tenant_id()
# Acquire S2S token and cache BEFORE creating spans
connection = CONNECTION_MANAGER.get_connection("SERVICE_CONNECTION")
token = await get_agentic_s2s_token(connection, tenant_id, agent_id)
_token_cache[f"{agent_id}:{tenant_id}"] = token
# Wrap spans in BaggageBuilder so the exporter can resolve the token
request = Request(content=user_message, session_id=None)
with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build():
invoke_scope = InvokeAgentScope.start(request, InvokeAgentScopeDetails(), agent_details)
with invoke_scope:
invoke_scope.record_input_messages([user_message])
invoke_scope.record_output_messages([response])
Important
The manual two-step flow (get_agentic_application_token + MSAL acquire_token_for_client) is required for S2S. AgenticUserAuthorization.get_token() returns a token scoped to 5a807f24-.../.default (Bot Framework), not the observability resource api://9b975845-.../.default : the S2S endpoint rejects it with 401 InvalidAudience.
- Use
context.activity.get_agentic_instance_id()andget_agentic_tenant_id()to read the agent and tenant from the activity (reads fromrecipientper SDK convention). - Acquire and cache the S2S token before creating spans. The exporter's
BatchSpanProcessormight flush before the handler finishes : if the token isn't cached yet, the export fails. - Wrap all A365 scopes in
BaggageBuilderso the exporter knows which agent and tenant to resolve tokens for. Without baggage, spans are silently dropped with "No spans with tenant/agent identity found."
Custom engine using OBO
Custom engine agents use standard app registrations with Azure Bot OAuth connections, not the agentic identity chain. By using OBO, the agent gets a user token through Azure Bot OAuth that's already scoped to the A365 observability API by the Bot Framework Token Service. A single getToken or GetTurnTokenAsync call returns the correctly scoped token, so you don't need exchangeToken.
Prerequisites
Entra app registration with Delegated API permissions. Add Agent365.Observability.OtelWrite (Delegated) and grant admin consent
Important
The agentId in the token cache must match the app registration's Client ID - not the activity's agenticAppId, which doesn't exist for custom engine agents. The export URL includes the agentId, and a mismatch causes HTTP 403.
Step 1: Environment and app configuration
The following examples show how to configure your app and runtime environment, including service connection values, tenant and client settings, and required authorization mappings.
# .env
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>
CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION
CONNECTIONSMAP__0__SERVICEURL=*
# Auth handler config : TYPE is required, name is uppercased by load_configuration_from_env
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__TYPE=UserAuthorization
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__SETTINGS__AZUREBOTOAUTHCONNECTIONNAME=oboConnectionProfile
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__OBOCONNECTIONPROFILE__SETTINGS__SCOPES=api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite
ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true
Important
load_configuration_from_env uppercases all environment variable keys. The handler name becomes OBOCONNECTIONPROFILE and you must reference it with that exact casing in auth_handlers and get_token() calls. Missing TYPE causes Auth handler ... not recognized or not configured at runtime.
Step 2: Configure the distro for OBO
The following examples show how to enable Agent 365 exporting, keep the exporter on the OBO endpoint, and register a custom TokenResolver that returns delegated tokens during export.
from microsoft.opentelemetry import use_microsoft_opentelemetry
_token_cache: dict[str, str] = {}
def token_resolver(agent_id: str, tenant_id: str) -> str | None:
return _token_cache.get(f"{agent_id}:{tenant_id}")
environ["ENABLE_A365_OBSERVABILITY_EXPORTER"] = "true"
use_microsoft_opentelemetry(
enable_a365=True,
a365_token_resolver=token_resolver,
a365_use_s2s_endpoint=False, # OBO uses /observability endpoint
a365_enable_observability_exporter=True,
)
Note
OBO mode requires jwt_authorization_middleware on the aiohttp Application (validates the inbound JWT (JSON Web Token) from Bot Framework). The S2S/emulator path shouldn't include this middleware.
from microsoft_agents.hosting.aiohttp import jwt_authorization_middleware
app = Application(middlewares=[jwt_authorization_middleware])
Step 3: Acquire the OBO token
The following examples show how to request a delegated OBO token from the configured Azure Bot OAuth connection, then cache it by app client and tenant for the exporter.
from microsoft_agents.hosting.core import (
AgentApplication, Authorization, MemoryStorage, TurnContext, TurnState,
)
from microsoft_agents.activity import load_configuration_from_env
from microsoft_agents.authentication.msal import MsalConnectionManager
from microsoft_agents.hosting.aiohttp import CloudAdapter
# Auth handlers are loaded from .env via load_configuration_from_env (see Environment config above)
agents_sdk_config = load_configuration_from_env(environ)
STORAGE = MemoryStorage()
CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config)
ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER)
AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config)
AGENT_APP = AgentApplication[TurnState](
storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config,
)
CLIENT_ID = environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", "")
TENANT_ID = environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID", "")
# Message handler : get_token returns a token already scoped to the observability API.
# The Azure Bot Token Service performs the OBO exchange internally based on the
# OAuth connection's configured scope. No manual MSAL exchange_token call is needed.
@AGENT_APP.activity("message", auth_handlers=["OBOCONNECTIONPROFILE"])
async def on_message(context: TurnContext, _state: TurnState):
token_response = await AGENT_APP.auth.get_token(context, "OBOCONNECTIONPROFILE")
# token_response.token has aud=<a365-observability-app-id>,
# scp=Agent365.Observability.OtelWrite
_token_cache[f"{CLIENT_ID}:{TENANT_ID}"] = token_response.token
Important
Azure Portal prerequisite: The Azure Bot OAuth connection named oboConnectionProfile must have its Scopes set to api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite. Without this setting, the token is scoped to the bot's own audience (api://botid-...) and export fails with HTTP 401 InvalidAudience.
Note
AGENT_APP.auth.get_token() returns the correctly-scoped token directly - no exchange_token() call is needed. The Bot Framework Token Service handles the OBO exchange when the OAuth connection scope targets the A365 observability resource.
Custom engine using S2S
Custom engine agents can use S2S (client credentials) to acquire an app-only token by using the service connection credentials. This method uses standard MSAL client credentials - no agentic identity chain required.
Prerequisites
- Azure AD app registration : Must be a custom engine (standard) app. Agent 365-enabled app registrations can't use plain
client_credentialsfor the observability resource (AADSTS82001). - Application permissions : Add
Agent365.Observability.OtelWrite(Application, not Delegated), and grant admin consent.
Important
The agentId used for caching must be the ServiceConnection's ClientId. The export URL is /observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces : a mismatch causes HTTP 403.
Step 1: Environment and app configuration
The following examples show how to configure your app and runtime environment, including service connection values, tenant and client settings, and required authorization mappings.
# .env
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=<your-client-id>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=<your-client-secret>
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=<your-tenant-id>
CONNECTIONSMAP__0__CONNECTION=SERVICE_CONNECTION
CONNECTIONSMAP__0__SERVICEURL=*
ENABLE_A365_OBSERVABILITY=true
ENABLE_A365_OBSERVABILITY_EXPORTER=true
Step 2: Configure the distro for S2S
The following examples show how to enable Agent 365 exporting, set the exporter to the S2S endpoint, and register a custom TokenResolver for token lookup during export.
from microsoft.opentelemetry import use_microsoft_opentelemetry
_token_cache: dict[str, str] = {}
def token_resolver(agent_id: str, tenant_id: str) -> str | None:
return _token_cache.get(f"{agent_id}:{tenant_id}")
use_microsoft_opentelemetry(
enable_a365=True,
a365_token_resolver=token_resolver,
a365_use_s2s_endpoint=True, # S2S uses /observabilityService endpoint
a365_enable_observability_exporter=True,
)
Step 3: Acquire the S2S token
The following examples show how to request an app-only access token for the observability resource by using the service connection credentials, then cache it by agent and tenant for the exporter.
# Force agentId to ServiceConnection ClientId (custom engine agents have no agenticAppId)
agent_id = os.environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID")
tenant_id = os.environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID")
connection = CONNECTION_MANAGER.get_connection("SERVICE_CONNECTION")
token = await connection.get_access_token(
resource_url="https://login.microsoftonline.com",
scopes=["api://9b975845-388f-4429-889e-eab1ef63949c/.default"],
)
_token_cache[f"{agent_id}:{tenant_id}"] = token
Step 4: Set baggage for span export
The Agent365 exporter requires baggage (tenant ID and agent ID) to be set on the span context. Without it, the exporter silently drops spans with the message No spans with tenant/agent identity found..
from microsoft.opentelemetry.a365.core import BaggageBuilder, InvokeAgentScope
# Baggage must wrap the span as a context manager
with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build():
invoke_scope = InvokeAgentScope.start(request, InvokeAgentScopeDetails(), agent_details)
with invoke_scope:
invoke_scope.record_input_messages([user_message])
invoke_scope.record_output_messages([response])