Tutorial 31

May 9, 2026 · View on GitHub

Level: Advanced · Time: 45 min · Prerequisites: Tutorial 02 (Trust & Identity), Azure subscription with Entra ID


What You'll Learn

  • Mapping AGT did:agentmesh: identities to Entra Agent ID objects
  • Synchronizing trust scores and kill-switch signals between AGT and Entra
  • Configuring Conditional Access policies for governed agents
  • Sponsor accountability through Agent365 directory integration

Why Bridge?

AGT and Entra Agent ID solve different parts of the agent governance problem:

ConcernAGTEntra Agent ID / Agent365
Identity formatdid:agentmesh:{hash} (Ed25519)Entra object ID (AAD)
Policy enforcementRuntime — per-tool-callDirectory — Conditional Access
Credential lifecycleShort-lived (15 min TTL), auto-rotatedOAuth 2.0 tokens, managed identity
Trust scoringBehavioral 0–1000 scoreN/A (binary active/suspended)
Kill switchInstant agent terminationDisable account in Entra
AuditAppend-only hash-chain logEntra sign-in + audit logs
Sponsor accountabilityPer-DID sponsor bindingPer-identity sponsor in directory
Shadow AI discoveryProcess/config/repo scanningAgent registry + Purview
ScopeAny cloud, any runtimeMicrosoft ecosystem + federated

Together they provide defense in depth: AGT handles runtime governance (policy, trust, sandboxing) while Entra Agent ID handles enterprise identity lifecycle (provisioning, access reviews, compliance).

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        ENTERPRISE CONTROL PLANE                      │
│                                                                      │
│  ┌──────────────┐    ┌──────────────────┐    ┌──────────────────┐  │
│  │  Agent365     │    │  Microsoft Entra  │    │  Microsoft       │  │
│  │  Dashboard    │◄──►│  Agent ID         │◄──►│  Purview         │  │
│  │              │    │                  │    │  (Compliance)    │  │
│  └──────┬───────┘    └────────┬─────────┘    └──────────────────┘  │
│         │                     │                                      │
│         │         ┌───────────┴───────────┐                         │
│         │         │  Entra Object ID       │                         │
│         │         │  + Sponsor             │                         │
│         │         │  + Conditional Access   │                         │
│         │         │  + API Permissions      │                         │
│         │         └───────────┬───────────┘                         │
│         │                     │                                      │
│         │              IDENTITY BRIDGE                               │
│         │         ┌───────────┴───────────┐                         │
│         │         │  EntraAgentRegistry    │                         │
│         │         │  did:mesh ↔ Entra OID  │                         │
│         │         └───────────┬───────────┘                         │
│         │                     │                                      │
│  ┌──────┴─────────────────────┴─────────────────────────────────┐  │
│  │                  AGT RUNTIME GOVERNANCE                        │  │
│  │                                                                │  │
│  │  ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────────┐  │  │
│  │  │ Policy  │ │ Trust    │ │ Audit   │ │ MCP Security     │  │  │
│  │  │ Engine  │ │ Scoring  │ │ Logger  │ │ Gateway          │  │  │
│  │  └─────────┘ └──────────┘ └─────────┘ └──────────────────┘  │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Roles and Responsibilities

What AGT Owns

ResponsibilityComponentDetails
Agent DID creationAgentIdentity.create()Ed25519 keypair + did:agentmesh:{hash}
Runtime policyPolicyEnginePer-tool-call allow/deny with rules
Trust scoringTrustEngineBehavioral 0–1000 score with decay
Tool-call governanceGovernanceMiddlewareRate limiting, injection detection, audit
MCP securityMcpSecurityScanner, McpGatewayTool poisoning, typosquatting, payload sanitization
Execution sandboxingExecutionRings4-tier privilege model (Ring 0–3)
Kill switchKillSwitchInstant termination on policy violation
Credential rotationCredentialManagerShort-lived bearer tokens (15 min)
Delegation chainsdelegate()Scoped child identities with depth limits
Audit loggingAuditLoggerAppend-only hash-chain with tamper detection

What Entra Agent ID / Agent365 Owns

ResponsibilityComponentDetails
Directory identityEntra Agent IDObject ID in tenant directory
Lifecycle managementAgent365Provisioning → access reviews → decommission
Conditional AccessEntra CA policiesLocation, device, risk-based access
Sponsor accountabilityEntra Agent IDHuman sponsor assigned per agent
Access reviewsEntra Identity GovernancePeriodic attestation by sponsors
OAuth 2.0 tokensEntra + MSALManaged identity, client credentials
API permissionsEntra app registrationsScoped Graph/API access
Shadow AI discoveryAgent365 + PurviewAgent registry, compliance scanning
Unified auditEntra sign-in logsAll auth events centralized
Compliance controlsPurview + DefenderDLP, threat protection, data governance

Shared Responsibilities (Bridge)

ResponsibilityAGT SideEntra Side
Identity mappingEntraAgentRegistry stores did:mesh ↔ entra_object_idEntra stores agent as directory object
Token exchangeEntraAgentID validates Entra JWT claimsEntra issues tokens via managed identity
Sponsor verificationAGT requires sponsor at DID creationEntra requires sponsor at identity creation
SuspensionAGT KillSwitch / trust score dropEntra disables account in directory
Audit correlationAGT logs include entra_object_idEntra logs include sign-in activity

Step 1 — Create AGT Identity with Entra Binding

from agentmesh import AgentIdentity
from agentmesh.identity.entra import EntraAgentRegistry, EntraAgentBlueprint

# 1. Set up the Entra registry for your tenant
registry = EntraAgentRegistry(tenant_id="your-tenant-id")

# 2. (Optional) Register a blueprint for consistent agent creation
registry.register_blueprint(EntraAgentBlueprint(
    display_name="Data Analyst Agent",
    description="Reads customer data and generates reports",
    default_capabilities=["read:customer-data", "write:reports"],
    require_sponsor=True,
    max_delegation_depth=2,
    conditional_access_policy="ca-policy-id-for-agents",
))

# 3. Create the AGT identity
identity = AgentIdentity.create(
    name="data-analyst-agent",
    sponsor="alice@contoso.com",
    capabilities=["read:customer-data", "write:reports"],
)
print(f"AGT DID: {identity.did}")  # did:agentmesh:a7f3b2c1...

# 4. Register the bridge mapping
#    The entra_object_id comes from your Entra Agent ID provisioning
#    (via Azure Portal, Graph API, or Agent365)
entra_identity = registry.register(
    agent_did=identity.did,
    agent_name="data-analyst-agent",
    entra_object_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",  # From Entra
    sponsor_email="alice@contoso.com",
    capabilities=["read:customer-data", "write:reports"],
    scopes=["https://graph.microsoft.com/.default"],
    blueprint_name="Data Analyst Agent",
)

Step 2 — Bootstrap from Azure Managed Identity (AKS)

When running on AKS with workload identity, AGT can auto-discover the Entra binding:

from agentmesh.identity.entra_agent_id import EntraAgentID

# Auto-discover from Azure IMDS (on AKS, VMs, Container Apps, etc.)
entra_agent = EntraAgentID.from_managed_identity(agent_did=identity.did)

# Or from environment variables
entra_agent = EntraAgentID.from_environment(agent_did=identity.did)

# Get the DID ↔ Entra mapping
mapping = entra_agent.to_did_mapping()
# {
#   "agent_did": "did:agentmesh:a7f3b2c1...",
#   "entra": {
#     "tenant_id": "your-tenant-id",
#     "client_id": "your-client-id"
#   },
#   "mapping_version": "1.0"
# }

AKS Workload Identity Setup

# 1. Create Kubernetes service account with Entra federated credential
apiVersion: v1
kind: ServiceAccount
metadata:
  name: agent-workload
  namespace: agents
  annotations:
    azure.workload.identity/client-id: "your-client-id"
  labels:
    azure.workload.identity/use: "true"

---
# 2. Pod spec with workload identity
apiVersion: v1
kind: Pod
metadata:
  name: data-analyst-agent
  namespace: agents
  labels:
    azure.workload.identity/use: "true"
spec:
  serviceAccountName: agent-workload
  containers:
    - name: agent
      image: your-registry.azurecr.io/data-analyst-agent:latest
      env:
        - name: AZURE_TENANT_ID
          value: "your-tenant-id"
        - name: AZURE_CLIENT_ID
          value: "your-client-id"
# 3. Create the federated credential in Entra
az identity federated-credential create \
  --name agent-fed-cred \
  --identity-name agent-managed-id \
  --resource-group agent-rg \
  --issuer "https://oidc.prod-aks.azure.com/your-oidc-issuer" \
  --subject "system:serviceaccount:agents:agent-workload" \
  --audiences "api://AzureADTokenExchange"

Step 3 — Token Validation at the Bridge

When an agent receives an Entra token (e.g., from another service), validate it and map to the AGT identity:

# Validate incoming Entra token
claims = entra_agent.validate_token(incoming_token)
# Validates: expiry, not-before, issuer (v1/v2 endpoints), audience

# Look up AGT identity from Entra object ID
agt_identity = registry.get_by_entra_id(claims["oid"])
if agt_identity and agt_identity.is_active():
    # Proceed with AGT policy enforcement using the mapped DID
    agt_identity.record_activity()

Important: EntraAgentID.validate_token() performs structural and claim-level validation only. For production deployments, add cryptographic signature verification using azure-identity or the Entra JWKS endpoint.

Step 4 — Lifecycle Synchronization

Keep AGT and Entra states in sync:

# When Entra suspends an agent → suspend in AGT
registry.suspend_agent(
    agent_did="did:agentmesh:a7f3b2c1...",
    reason="Entra Conditional Access violation"
)

# When AGT kill switch fires → disable in Entra
# Use Graph API: PATCH /servicePrincipals/{entra_object_id}
# with { "accountEnabled": false }
# (Requires Directory.ReadWrite.All or Application.ReadWrite.All permission)

# When sponsor re-approves → reactivate
registry.reactivate_agent(agent_did="did:agentmesh:a7f3b2c1...")

Audit Correlation

AGT audit events include the Entra object ID for cross-system correlation:

# AGT audit record
audit_record = entra_identity.to_audit_record()
# {
#   "agent_did": "did:agentmesh:a7f3b2c1...",
#   "entra_object_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
#   "tenant_id": "your-tenant-id",
#   "sponsor_email": "alice@contoso.com",
#   "status": "active",
#   "capabilities": ["read:customer-data", "write:reports"],
#   "scopes": ["https://graph.microsoft.com/.default"],
#   "last_activity": "2026-04-16T01:00:00Z"
# }

Step 5 — Access Verification with Entra Scopes

Combine AGT policy checks with Entra scope verification:

from agentmesh import PolicyEngine

# Load AGT policies
policy_engine = PolicyEngine(config_path="governance.yaml")

# Combined verification: Entra scope + AGT policy
def verify_tool_call(agent_did: str, tool_name: str, params: dict) -> bool:
    # 1. Check Entra scope
    allowed, reason = registry.verify_access(
        agent_did=agent_did,
        required_scope="https://graph.microsoft.com/Files.Read"
    )
    if not allowed:
        print(f"Entra denied: {reason}")
        return False

    # 2. Check AGT policy
    decision = policy_engine.evaluate(agent_did, tool_name, params)
    if not decision.allowed:
        print(f"AGT policy denied: {decision.reason}")
        return False

    return True

Step 6 — Group Membership Sync via Graph API

Automatically map Entra group memberships to AGT capabilities:

from agentmesh.identity.entra_graph import EntraGraphClient, build_group_scope_map

# 1. Get a Graph API token (via managed identity, workload identity, etc.)
token = entra_agent.get_agent_token(scope="https://graph.microsoft.com/.default")

# 2. Create the Graph client
graph_client = EntraGraphClient(access_token=token)

# 3. Define group-to-capability mapping (keyed by Entra group object ID)
group_scope_map = build_group_scope_map({
    "aaaaaaaa-1111-2222-3333-444444444444": ["read:customer-data", "read:reports"],
    "bbbbbbbb-1111-2222-3333-444444444444": ["write:reports", "export:csv"],
})

# 4. Sync — fetches groups from Graph, maps to capabilities, preserves manual caps
capabilities = registry.sync_group_memberships(
    agent_did=identity.did,
    graph_client=graph_client,
    group_scope_map=group_scope_map,
)
print(f"Updated capabilities: {capabilities}")
# ['export:csv', 'manual:cap', 'read:customer-data', 'read:reports', 'write:reports']

Note: The Graph API call requires GroupMember.Read.All or Directory.Read.All permission on the Entra application. Group sync only updates AGT capabilities — Entra API scopes remain unchanged.

Step 7 — Validate Bridge Configuration

Before going to production, validate the bridge mapping is complete:

valid, issues = registry.validate_bridge_configuration(agent_did=identity.did)
if not valid:
    print(f"Bridge configuration issues: {issues}")
    # e.g., ['Missing entra_app_id — Agent365 may not resolve the agent']
else:
    print("Bridge configuration OK — ready for enterprise deployment")

Known Gaps and Limitations

GapStatusWorkaround
Graph API group membership sync✅ BuiltEntraGraphClient.get_group_memberships() + EntraAgentRegistry.sync_group_memberships() — maps Entra groups to AGT capabilities
Agent365 native integrationConfiguration validatedEntraAgentRegistry.validate_bridge_configuration() checks bridge mapping completeness; end-to-end Agent365 testing pending
Bidirectional lifecycle syncOne-way (manual)Use Azure Event Grid or Logic Apps to sync Entra state changes → AGT kill switch
Graph API service principal disableNot in AGTUse Graph API directly: PATCH /servicePrincipals/{id} with accountEnabled: false
Entra bridge in non-Python language packagesPython-onlyTS, .NET, Rust, and Go packages need EntraAgentRegistry and EntraAgentID ported
DID format inconsistencydid:agentmesh:* (Python, .NET) vs did:agentmesh:* (TS, Rust, Go)Both formats work; standardization planned for v4.0
Cryptographic token verificationClaim-level onlyAdd azure-identity for JWKS-based signature verification

Platform Independence Note

While this tutorial focuses on Microsoft Entra, AGT's identity layer is platform-independent. The same bridging pattern applies to:

  • AWS IAM Identity Center — map did:agentmesh:* ↔ IAM role ARN
  • Google Cloud Workload Identity — map did:agentmesh:* ↔ service account email
  • Okta Workforce Identity — map did:agentmesh:* ↔ Okta user/app ID
  • SPIFFE/SPIRE — map did:agentmesh:* ↔ SPIFFE ID (see identity docs)

AGT's EntraAgentRegistry pattern can be adapted for any enterprise IdP. We welcome community contributions for AWS, GCP, and Okta adapters.

Next Steps