Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ The foundation for distributed tracing in agent applications. Built on OpenTelem
| `InferenceScope` | Trace LLM/AI model inference calls |
| `ExecuteToolScope` | Trace tool execution operations |
| `BaggageBuilder` | Fluent API for context propagation across async boundaries |
| `ResolvedInvocationIdentity` | Process-local validated caller and target identity for span enrichment |

**Data Classes:**

Expand Down Expand Up @@ -181,6 +182,8 @@ Framework-specific instrumentations that integrate with the observability core:
| `observability-extensions-openai` | Instrument OpenAI Agents SDK | [design.md](../packages/agents-a365-observability-extensions-openai/docs/design.md) |
| `observability-hosting` | Hosting-specific observability utilities | [design.md](../packages/agents-a365-observability-hosting/docs/design.md) |

The hosting package can opt into automatic invocation identity resolution through `ObservabilityHostingManager.configure(adapter, { enableInvocationIdentity: true })`. Resolved identity remains process-local and is applied to core and extension spans by the observability span processor.

### 4. Tooling (`@microsoft/agents-a365-tooling`)

> **Detailed documentation**: [packages/agents-a365-tooling/docs/design.md](../packages/agents-a365-tooling/docs/design.md)
Expand Down
29 changes: 27 additions & 2 deletions packages/agents-a365-observability-hosting/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ This document describes the architecture and design of the `@microsoft/agents-a3

## Overview

The observability hosting package provides hosting-specific utilities for integrating observability with the Microsoft Agents Hosting SDK. It bridges the gap between `TurnContext` and OpenTelemetry baggage/scope creation.
The observability hosting package provides hosting-specific utilities for integrating observability with the Microsoft Agents Hosting SDK. It bridges the gap between `TurnContext`, request-local invocation identity, and OpenTelemetry baggage/scope creation.

See [Automatic Invocation Identity](invocation-identity.md) for the opt-in caller and target identity feature.

## Architecture

Expand All @@ -24,6 +26,11 @@ The observability hosting package provides hosting-specific utilities for integr
│ │ Extract baggage │───▶│ Populate builder │ │
│ │ pairs from context │ │ from context │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ InvocationIdentityMiddleware + Resolver │ │
│ │ Validated claims/Activity → private OTel context │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Expand All @@ -35,6 +42,18 @@ The observability hosting package provides hosting-specific utilities for integr

## Key Components

### InvocationIdentityMiddleware

The opt-in middleware resolves identity once per turn, enters a private OpenTelemetry context, and runs downstream middleware and agent logic inside that context. It never places resolved identity into W3C baggage.

```typescript
new ObservabilityHostingManager().configure(adapter, {
enableInvocationIdentity: true,
});
```

The core `SpanProcessor` reads the resolved identity from the supplied parent context and stamps human, caller-agent, target-agent, tenant, and invocation-role attributes onto every local span.

### BaggageBuilderUtils ([BaggageBuilderUtils.ts](../src/utils/BaggageBuilderUtils.ts))

Utilities to populate `BaggageBuilder` from a `TurnContext`:
Expand Down Expand Up @@ -183,6 +202,12 @@ async function onMessage(turnContext: TurnContext, turnState: TurnState) {
```
src/
├── index.ts # Public API exports
├── identity/
│ └── InvocationIdentityResolver.ts # Trusted identity classification
├── middleware/
│ ├── InvocationIdentityMiddleware.ts # Per-turn identity context
│ ├── BaggageMiddleware.ts
│ └── OutputLoggingMiddleware.ts
├── utils/
│ ├── BaggageBuilderUtils.ts # BaggageBuilder population utilities
│ ├── TurnContextUtils.ts # Low-level extraction functions
Expand All @@ -196,4 +221,4 @@ src/
- `@microsoft/agents-a365-observability` - BaggageBuilder, OpenTelemetryConstants
- `@microsoft/agents-a365-runtime` - Runtime utilities
- `@microsoft/agents-hosting` - TurnContext type
- `@microsoft/agents-activity` - RoleTypes enum
- `@microsoft/agents-activity` - Activity and role contracts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Automatic Invocation Identity

Automatic invocation identity enrichment resolves the caller and target identities for a hosted agent turn and adds them to every OpenTelemetry span created in that turn.

## Enable the feature

The feature is opt-in:

```typescript
new ObservabilityHostingManager().configure(adapter, {
enableInvocationIdentity: true,
});
```

Omitting `enableInvocationIdentity` and setting it to `false` are equivalent. Resolver options do not enable the feature. Baggage and output logging remain separately controlled:

```typescript
new ObservabilityHostingManager().configure(adapter, {
enableInvocationIdentity: true,
enableBaggage: true,
enableOutputLogging: true,
});
```

Set `enableInvocationIdentity: false` to roll back without changing legacy baggage, output logging, or scope-helper behavior.

## Trust boundary

The hosting middleware assumes the standard `authorizeJWT` hosting path and therefore defaults `turnContextIdentityTrustSource` to `StandardAuthorizeJwt`. The middleware only reads `TurnContext.identity` and Activity identity fields when the identity is a nonempty object and is not the anonymous development identity.

Custom hosts must disable that assumption and provide an already-validated principal:

```typescript
new ObservabilityHostingManager().configure(adapter, {
enableInvocationIdentity: true,
turnContextIdentityTrustSource: TurnContextIdentityTrustSource.None,
resolveValidatedPrincipal: async (turnContext) => {
return validatePrincipalFromCustomHost(turnContext);
},
});
```

The resolver never decodes the raw authorization token. It does not use `sub`, email, UPN, `from.id`, channel IDs, or the target agent as a caller identity.

## Classification

Validated claims use the following classification:

| Evidence | Role and caller identity |
| --- | --- |
| `idtyp=user`, valid `oid`, delegated `scp`, no Agent ID marker | Human; `oid` becomes `user.id` |
| Above with `xms_sub_fct` containing `13` | Agent; `oid` becomes `microsoft.a365.caller.agent.user.id` |
| User token with `xms_act_fct` containing `11` or `xms_par_app_azp` | Agent/OBO; `oid` remains the human and the parent app becomes the caller blueprint |
| `idtyp=app`, nonempty `roles`, and an Agent ID marker | Agent; the parent app becomes the caller blueprint |
| Ordinary service application | No trusted caller classification |

Trusted Activity roles are normalized by removing whitespace, `_`, and `-`, then lowercasing:

| Activity role | Classification |
| --- | --- |
| `user` | Human |
| `bot`, `skill`, `agent`, `agenticAppInstance`, `agenticUser` | Agent |
| Missing or unsupported | Unknown |
| Event activity | Unknown unless explicitly configured |

`ContinueConversation` is not automatically classified as Event. Configure `invocationRole: InvocationRole.Event` for a known autonomous event.

## Precedence

Role precedence:

1. Explicit `invocationRole`
2. Application or hosting validated principal
3. Trusted non-Event Activity role
4. Unknown

Caller-field precedence is application principal, hosting principal, then Activity. Target-field precedence is the current trusted Activity followed by configured target fallback.

Conflicts are reported through `onIdentityConflict`. The callback receives the field, normalized values, and the winning and losing resolution sources.

## A2A, OBO, and agent-user identity

- Direct human calls use `user.id`.
- An OBO call preserves both `user.id` and the immediate caller-agent blueprint.
- An agent-user call uses `microsoft.a365.caller.agent.user.id`, never `user.id`.
- App-only callers use caller instance or blueprint attributes when available.
- Event calls carry target execution identity and do not fabricate a caller.

Manual `CallerDetails` remain an escape hatch. Explicit nonblank scope values override automatically resolved values.

## Context and baggage

Resolved identity is stored in a private process-local OpenTelemetry context key. It is not added to W3C baggage.

When local identity exists, baggage cannot provide or overwrite these fields:

- `microsoft.a365.invocation.role`
- `microsoft.tenant.id`
- `gen_ai.agent.id`
- `microsoft.agent.user.id`
- `microsoft.a365.agent.blueprint.id`
- `user.id`
- `microsoft.a365.caller.agent.id`
- `microsoft.a365.caller.agent.user.id`
- `microsoft.a365.caller.agent.blueprint.id`

Display metadata continues to use legacy behavior.

## Validation and diagnostics

All identity join IDs must be non-nil UUIDs and are normalized to lowercase. Strict validation can be enabled with `strictIdentityValidation: true`. Without strict validation, missing identity warns and the turn continues.

Identity-enriched `invoke_agent` spans warn for:

- `missing_human_identity`
- `missing_agent_identity`
- `unknown_invocation_role`
- `missing_event_execution_identity`

Warnings use `console.warn` so they remain visible when the SDK log level is `none`. Repeated warnings are deduplicated by tenant, target agent, and reason.

## Backend normalization

The telemetry backend should compute:

```text
effectivePrincipalId =
user.id
?? microsoft.a365.caller.agent.user.id
?? microsoft.a365.caller.agent.id
?? microsoft.a365.caller.agent.blueprint.id
?? (invocation.role == Event
? microsoft.agent.user.id
?? gen_ai.agent.id
?? microsoft.a365.agent.blueprint.id
: undefined)
```

The backend must retain `principalType` and `principalSource`. Event target fallback uses `principalSource=target-agent`. This backend normalization is not implemented by the Node.js SDK.
4 changes: 3 additions & 1 deletion packages/agents-a365-observability-hosting/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"files": [
"dist",
"README.md",
"CHANGELOG.md"
"CHANGELOG.md",
"docs"
],
"scripts": {
"build": "npm run build:cjs && npm run build:esm",
Expand All @@ -44,6 +45,7 @@
"dependencies": {
"@microsoft/agents-a365-observability": "workspace:*",
"@microsoft/agents-a365-runtime": "workspace:*",
"@microsoft/agents-activity": "catalog:",
"@microsoft/agents-hosting": "catalog:",
"@opentelemetry/api": "catalog:"
},
Expand Down
Loading
Loading