Install
$ agentstack add skill-microsoft-agent365-skills-instrument-observability ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Instrument A365 Observability
> Trigger phrases — any of these will activate this skill automatically: > - "instrument observability for this agent" > - "add a365 observability to this agent" > - "add observability to this agent" > - "set up tracing for this agent" > - "make this agent visible in microsoft defender" > - "enable agent 365 telemetry" > - "wire up opentelemetry for this agent" > - "add observability to this .net agent" > - "add observability to this node.js agent" > - "add a365 observability to this python agent"
Overview
This skill instruments Microsoft Agent 365 observability into an existing agent codebase without disrupting the agent's core logic. It:
- Detects the agent type (.NET AgentFramework, Node.js, or Python)
- Installs the correct A365 observability packages (core + hosting + optional extensions)
- Wires observability in the entry point
- Adds BaggageBuilder context or BaggageMiddleware to message handlers
- Implements the agentic token resolver with caching
- Adds manual instrumentation scopes (InvokeAgentScope, InferenceScope, ExecuteToolScope — required for store publishing)
- Updates configuration files with observability settings
- Validates the build passes
> Store publishing requirement: The Agent 365 store validation requires InvokeAgentScope, > InferenceScope, and ExecuteToolScope to be implemented. This skill wires them.
All changes are additive and idempotent — rerunning the skill is safe.
Phase 0: Load Detection Cache and Validate
> Task-list display (applies throughout this skill). This skill creates tasks inline via **TaskCreate** — "..." markers at the start of each phase, and marks them complete at phase end. The user must see this progress visibly. Each TaskCreate line corresponds to one checklist item; exactly one item in_progress at a time. > - Claude Code: TaskCreate is in allowed-tools — calling it renders a native checklist UI; subsequent TaskUpdate calls flip statuses. > - VS Code Copilot Chat / GitHub Copilot CLI: allowed-tools is ignored — before Phase 0.1, scan this SKILL.md for all **TaskCreate** — "..." lines and emit a markdown checklist in chat upfront (- [ ] Load detection cache…, - [ ] Determine agent kind…, etc.); flip items to - [x] as each phase completes.
TaskCreate — "Load detection cache and validate with user"
Step 0.1 — Triage the workspace
Run in parallel:
- Glob
**/*.csproj,package.json,requirements.txt,pyproject.toml,src/**/*.ts,**/*.cs,**/*.py→hasProjectFiles. - Read
.a365-workspace-detection.local.json→cacheState(freshifdetectedAtdoesn't patch your LLM library directly. The skill will still wireuseMicrosoftOpenTelemetry/UseMicrosoftOpenTelemetry(OTel SDK + A365 exporter), but you'll need to manually wrap each LLM call withInferenceScope.start(...)to capturegen_ai.spans. See-observability.md` § 'InferenceScope — Manual Wrapping' for the pattern." Continue to Phase 2.
- TaskUpdate — Mark complete and report detected agent type (+ any soft-warn) to user.
Phase 2: Install A365 Observability Packages
TaskCreate — "Install A365 observability packages"
All languages converge on a single unified distro that re-exports the legacy A365 observability + hosting types and auto-instruments common LLM SDKs:
| Language | Install command | S2S extra (FMI token chain) | |----------|-----------------|------------------------------| | .NET | dotnet add package Microsoft.OpenTelemetry | dotnet add package Azure.Identity Microsoft.Identity.Client | | Node.js | npm install @microsoft/opentelemetry | npm install @azure/msal-node @azure/identity | | Python | pip install microsoft-opentelemetry | pip install msal azure-identity httpx |
Do not install legacy *.Observability.Runtime / -hosting / -extensions-* packages alongside the unified distro — the distro re-exports their types and mixing the two produces CS0433 duplicate-type errors (.NET) or duplicate spans (Node.js / Python). After install, verify the package appears in the manifest (*.csproj / package.json / requirements.txt or pyproject.toml). pip install does not update the dependency manifest — prefer uv add microsoft-opentelemetry (or poetry add ...) for Python.
Python — Google ADK gotcha: if pyproject.toml lists google-adk, uv sync will backtrack for minutes resolving the OTel graph. Pin OTel via [tool.uv] override-dependencies — see python-observability.md → "Google ADK projects — pin the OTel stack" for the exact block. Other Python stacks (AgentFramework, LangChain, OpenAI, Claude, Semantic Kernel) don't need this.
Full per-language package tables, version constraints, and the LangChain extras flag live in the references — see the "Required packages" section of:
${CLAUDE_PLUGIN_ROOT}/skills/instrument-observability/references/dotnet-observability.md${CLAUDE_PLUGIN_ROOT}/skills/instrument-observability/references/nodejs-observability.md${CLAUDE_PLUGIN_ROOT}/skills/instrument-observability/references/python-observability.md
TaskUpdate — Mark complete.
Phase 3: Wire Observability in Entry Point
TaskCreate — "Wire observability in entry point"
> Pre-existing placeholders: As of CLI 1.1, a365 setup all auto-writes Agent365Observability placeholder sections to appsettings.json (.NET) or .env (Node.js/Python). Before creating config from scratch, check if placeholders already exist and fill in values rather than duplicating the section.
For .NET AgentFramework
- Read the current entry point (
Program.csor detected file).
- Edit — Add observability wiring following the reference pattern in
dotnet-observability.md:
- Add
using Microsoft.OpenTelemetry;toProgram.cs. - OBO / agentic-user path (AI Teammate AND Standard .NET agents): Call
builder.UseMicrosoftOpenTelemetry(o => { ... })witho.Exporters = ExportTarget.Agent365 | ExportTarget.Console(Dev) orExportTarget.Agent365(Production). The distro auto-registersIExporterTokenCachein DI — noAddAgenticTracingExporter()call needed. Leaveo.Agent365.Exporter.UseS2SEndpointat its default (false) — the exporter POSTs to/observability/which the OBO token cache authenticates. Also set"EnableAgent365Exporter": trueinappsettings.jsonto activate the backend exporter — the SDK defaults this tofalsewhen absent, so without it the exporter is wired but inert. - Required:
IChatClient.UseOpenTelemetry()— when registering theIChatClient(e.g. Azure OpenAI), chain.AsBuilder().UseFunctionInvocation().UseOpenTelemetry(sourceName: null, cfg => cfg.EnableSensitiveData = true).Build(). This is what makes the AI SDK emit thegen_ai.inferenceandgen_ai.toolspans thatInvokeAgentScope(Phase 5.5) anchors as children. Skipping this means no LLM spans appear in MAC, even with everything else wired — theInvokeAgentparent becomes a hollow span.EnableSensitiveData = trueincludes prompts/completions in span attributes (PII consideration — set tofalsefor regulated data). - S2S path: First Write the two scaffold files from the reference doc —
Observability/ObservabilityServiceExtensions.cs(DI extension withAddAgent365Observability()usingServiceTokenCacheand conditionalObservabilityTokenService) andObservability/ObservabilityTokenService.cs(background service that acquires the Observability API token via the MSAL FMI 3-hop chain with.WithFmiPath()targeting scopeapi://9b975845-388f-4429-889e-eab1ef63949c/.default, supports MSI with client-secret fallback). Then callbuilder.Services.AddAgent365Observability();andbuilder.UseMicrosoftOpenTelemetry(...)with token resolver reading from theServiceTokenCache. Critical: Seto.Agent365.Exporter.UseS2SEndpoint = truein the options callback — without this, the exporter posts to the wrong path (/observability/instead of/observabilityService/) and gets HTTP 401. See "Known Issues" section. - Optionally register
adapter.Use(new BaggageTurnMiddleware())(OBO path only) to auto-populate baggage on every request - Mark all new lines with:
// A365 Observability — best-effort instrumentation (verify against official sample)
- Preserve all existing code — only add new lines, never remove.
For Node.js
- Read the current entry point (
index.ts,app.ts, or detected file).
- Edit — Add observability initialization following the reference pattern in
nodejs-observability.md:
- Import
useMicrosoftOpenTelemetry,shutdownMicrosoftOpenTelemetry,configureA365Hosting, andAgenticTokenCacheInstance— all from@microsoft/opentelemetry(single package as of GA 1.0; do NOT import from the legacy-observability,-hosting, or-runtimepackages). - OBO / agentic-user path: Call
useMicrosoftOpenTelemetry({ a365: { enabled: true, enableObservabilityExporter: true, tokenResolver } })before any LLM/framework imports. Bothenabled: trueANDenableObservabilityExporter: trueare required in 1.0+ to actually export spans. WiretokenResolvertoAgenticTokenCacheInstance.getObservabilityToken(agentId, tenantId) ?? ''. - S2S path: First Write
observability/token-cache.ts(in-memory token cache withcacheToken/getCachedToken/tokenResolver) andobservability/observability-token-service.tsusing the scaffold pattern fromnodejs-observability.md(S2S section). This module acquires the Observability API token via MSAL FMI 3-hop chain (@azure/msal-nodewithfmiPathparameter, targeting scopeapi://9b975845-388f-4429-889e-eab1ef63949c/.default, supports MSI with client-secret fallback) and refreshes it every 50 min. Then calluseMicrosoftOpenTelemetry({ a365: { enabled: true, enableObservabilityExporter: true, useS2SEndpoint: true, tokenResolver: a365TokenResolver } }).useS2SEndpoint: trueis now a first-class option (1.0+); the old workaround with customAgent365ExporterviaspanProcessorsandENABLE_A365_OBSERVABILITY_EXPORTER=falseis no longer needed for new instrumentation. If the old workaround is already present in an existing agent, leave it in place — do not delete code as part of this additive skill; flag it in the final summary as a candidate for cleanup if the user explicitly asks to migrate. - Both paths: Call
configureA365Hosting(adapter, { enableBaggage: true })once at startup to registerBaggageMiddleware. This replaces manualadapter.use(new BaggageMiddleware())and removes the need forBaggageBuilderUtils.fromTurnContextin handlers. - Both paths: Register
SIGTERM/SIGINThandlers callingawait shutdownMicrosoftOpenTelemetry()to flush pending spans on shutdown. - Auto-instrumentation note: Do NOT call
OpenAIAgentsTraceInstrumentor.enable()orLangChainTraceInstrumentor.instrument()— these are auto-enabled in 1.0+ and manual calls cause duplicate spans. To opt out, setinstrumentationOptions: { openaiAgents: { enabled: false } }. - Mark all new lines with:
// A365 Observability — best-effort instrumentation (verify against official sample)
- Preserve all existing code — only add new lines, never remove.
For Python
- Read the current entry point (
app.py,host_agent_server.py, or detected file).
- Edit — Add observability configuration following the reference pattern in
python-observability.md:
- Import
use_microsoft_opentelemetryfrommicrosoft.opentelemetry(single unified package; do NOT import from the legacymicrosoft_agents_a365.*namespace). - OBO / agentic-user path: Call
use_microsoft_opentelemetry(enable_a365=True, a365_enable_observability_exporter=True, a365_token_resolver=...). Bothenable_a365=TrueANDa365_enable_observability_exporter=Trueare required in 1.0+ to actually export spans. Wirea365_token_resolvertoAgenticTokenCache().get_observability_tokenfrommicrosoft.opentelemetry.a365.hosting.token_cache_helpers(or a custom resolver reading fromtoken_cache.py). - S2S path: First Write
observability/token_cache.py(in-memory token cache withcache_token/get_cached_token) andobservability/observability_token_service.pyusing the scaffold pattern frompython-observability.md(S2S section). This module acquires the Observability API token via a 3-hop FMI chain: direct HTTP POST withfmi_pathfor Hops 1+2 (MSAL Python does not properly serializefmi_path— known limitation), thenmsal.ConfidentialClientApplicationfor Hop 3, targeting scopeapi://9b975845-388f-4429-889e-eab1ef63949c/.default, supports MSI with client-secret fallback, refreshes every 50 min via anasynciobackground task. Then calluse_microsoft_opentelemetry(enable_a365=True, a365_enable_observability_exporter=True, a365_use_s2s_endpoint=True, a365_token_resolver=...).a365_use_s2s_endpoint=Trueis now a first-class kwarg — no workaround needed. Schedulerun_token_service()as an asyncio task and callacquire_initial_token()in your aiohttp lifespan startup. Also installmsal,azure-identity, andhttpx. - Both paths: Call
ObservabilityHostingManager.configure(adapter.middleware_set, ObservabilityHostingOptions(enable_baggage=True))once at startup to auto-populate baggage fromTurnContext. Note:enable_baggagedefaults toFalse— must be explicitly set toTrue. - Auto-instrumentation note: Do NOT call legacy
*Instrumentor().instrument()methods for LangChain/OpenAI/SK/AgentFramework — these are auto-enabled in 1.0+ and manual calls cause duplicate spans. - Mark all new lines with:
# A365 Observability — best-effort instrumentation (verify against official sample)
- Preserve all existing code — only add new lines, never remove.
- TaskUpdate — Mark complete.
Phase 4: Add BaggageBuilder Context to Message Handler
TaskCreate — "Add BaggageBuilder context to message handler"
> Skip this phase if BaggageMiddleware was registered in Phase 3 — the middleware handles > baggage propagation automatically for every request.
> Auth mode note: All three authMode values use authHandlerName: "AGENTIC" in the > code — the token exchange call is identical. The identity in traces is determined by Azure AD > provisioning and the incoming token. Add an inline comment indicating which mode was chosen.
For .NET AgentFramework
- Read the detected message handler file.
- Edit — Follow the reference pattern in
dotnet-observability.md(full code sample under "Agent Class — Message Handler (OBO Path)"):
OBO path (obo / agentic-user) — applies to both AI Teammate agents and Standard .NET agents:
- Inject
IExporterTokenCachein the constructor (auto-registered by the distro — noAddAgenticTracingExporter()call needed). - Inject
IConfiguration(for blueprint/observability config) andILogger. - Resolve agent ID for BOTH auth paths — agentic instance ID from the Activity for agentic turns, decoded from the auth token via
Utility.ResolveAgentIdentity(context, authToken)for non-agentic turns (the SDK names the second parameter genericallyauthToken— it accepts both OBO tokens and agentic-path tokens returned byUserAuthorization.GetTurnTokenAsync). Do NOT fall back toGuid.Empty.ToString()— that creates a synthetic identity the exporter cannot authenticate, polluting traces with"No token obtained. Skipping export for this identity."warnings.
```csharp string? resolvedAgentId = null; if (turnContext.Activity.IsAgenticRequest()) { resolvedAgentId = turnContext.Activity.GetAgenticInstanceId(); } else if (!string.IsNullOrEmpty(authHandlerName
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: microsoft
- Source: microsoft/agent365-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.