{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "How Microsoft Foundry Is Turning Long-Running Agents Into an Enterprise Platform",
      "slug": "how-microsoft-foundry-is-turning-long-running-agents-into-an",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-07-10T13:42:04.588Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# How Microsoft Foundry Is Turning Long-Running Agents Into an Enterprise Platform\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation walkthrough focused on long-running agent behavior, run-state lifecycle, governance, and observability. The emphasis is not on chat UX, but on the enterprise runtime concerns that appear when agents call tools, pause, resume, and operate under policy."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install -q pandas"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import time\n",
        "import json\n",
        "from dataclasses import dataclass, asdict\n",
        "from datetime import datetime, timezone\n",
        "from typing import Optional, List, Dict, Any\n",
        "\n",
        "import pandas as pd"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Enterprise agent platform layers\n",
        "\n",
        "The blog argues that Microsoft Foundry is becoming a control plane for durable agent workloads. This cell converts the architecture into a simple tabular model so you can inspect the platform layers and their responsibilities."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "layers = [\n",
        "    {\n",
        "        \"layer\": \"experience\",\n",
        "        \"components\": \"Business apps, copilots, custom front ends\",\n",
        "        \"responsibility\": \"User interaction and request initiation\"\n",
        "    },\n",
        "    {\n",
        "        \"layer\": \"gateway\",\n",
        "        \"components\": \"Azure API Management AI Gateway\",\n",
        "        \"responsibility\": \"Auth, quotas, routing, correlation IDs, policy enforcement\"\n",
        "    },\n",
        "    {\n",
        "        \"layer\": \"agent_runtime\",\n",
        "        \"components\": \"Foundry project, hosted agents, Agent Framework patterns\",\n",
        "        \"responsibility\": \"Run execution, lifecycle management, tool orchestration\"\n",
        "    },\n",
        "    {\n",
        "        \"layer\": \"tooling\",\n",
        "        \"components\": \"MCP tools, enterprise APIs, SAP/ITSM/approval systems\",\n",
        "        \"responsibility\": \"External actions and long-running operations\"\n",
        "    },\n",
        "    {\n",
        "        \"layer\": \"state_observability\",\n",
        "        \"components\": \"Run state store, logs, dashboards, alerts\",\n",
        "        \"responsibility\": \"Persistence, telemetry, incident response\"\n",
        "    },\n",
        "    {\n",
        "        \"layer\": \"knowledge\",\n",
        "        \"components\": \"Foundry IQ, Work IQ\",\n",
        "        \"responsibility\": \"Permission-aware organizational grounding\"\n",
        "    }\n",
        "]\n",
        "\n",
        "df_layers = pd.DataFrame(layers)\n",
        "df_layers"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Minimal Foundry-style run lifecycle\n",
        "\n",
        "This example mirrors the blog's core idea: the run is the unit of work, and a long-running tool call becomes a dependency that changes run state. Watch for the `requires_action` transition, which is where governance and operator visibility become critical."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "@dataclass\n",
        "class Run:\n",
        "    id: str\n",
        "    state: str\n",
        "    tool_call_id: Optional[str] = None\n",
        "\n",
        "run = Run(id=\"run_001\", state=\"queued\")\n",
        "print(f\"{run.id}: {run.state}\")\n",
        "\n",
        "for state in [\"in_progress\", \"requires_action\", \"in_progress\", \"completed\"]:\n",
        "    time.sleep(0.2)\n",
        "    run.state = state\n",
        "    if state == \"requires_action\":\n",
        "        run.tool_call_id = \"mcp_call_42\"\n",
        "        print(f\"{run.id}: waiting on MCP tool {run.tool_call_id}\")\n",
        "    else:\n",
        "        print(f\"{run.id}: {run.state}\")\n",
        "\n",
        "print(\"\\nFinal run object:\")\n",
        "print(asdict(run))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Polling a hosted-agent run\n",
        "\n",
        "The next example simulates operator-facing polling of a run over time. This is useful for validating how a client or dashboard might distinguish normal progress from a pause caused by a long-running MCP tool."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "events = [\n",
        "    {\"state\": \"queued\"},\n",
        "    {\"state\": \"in_progress\"},\n",
        "    {\"state\": \"requires_action\", \"tool\": \"sap.approval.submit\", \"call_id\": \"tool_9\"},\n",
        "    {\"state\": \"in_progress\"},\n",
        "    {\"state\": \"completed\"},\n",
        "]\n",
        "\n",
        "history = []\n",
        "for event in events:\n",
        "    time.sleep(0.2)\n",
        "    state = event[\"state\"]\n",
        "    history.append(event)\n",
        "    if state == \"requires_action\":\n",
        "        print(f\"Run paused for tool={event['tool']} call_id={event['call_id']}\")\n",
        "    else:\n",
        "        print(f\"Run state={state}\")\n",
        "    if state in {\"completed\", \"failed\", \"cancelled\"}:\n",
        "        break\n",
        "\n",
        "print(\"\\nEvent history:\")\n",
        "pd.DataFrame(history)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Structured logging for run and tool correlation\n",
        "\n",
        "The blog emphasizes that long-running agents become hard to troubleshoot without structured logs. This example emits JSON log records containing run IDs, state, and tool call IDs so that downstream systems can correlate activity across services."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def log_event(run_id: str, state: str, tool_call_id: Optional[str] = None) -> None:\n",
        "    record = {\n",
        "        \"ts\": datetime.now(timezone.utc).isoformat(),\n",
        "        \"run_id\": run_id,\n",
        "        \"state\": state,\n",
        "        \"tool_call_id\": tool_call_id,\n",
        "        \"service\": \"foundry-agent\",\n",
        "    }\n",
        "    print(json.dumps(record))\n",
        "\n",
        "log_event(\"run_001\", \"queued\")\n",
        "log_event(\"run_001\", \"requires_action\", \"mcp_call_42\")\n",
        "log_event(\"run_001\", \"completed\", \"mcp_call_42\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Foundry project prerequisites as governed deployment metadata\n",
        "\n",
        "The original post included a PowerShell object for deployment prerequisites. Here it is translated into Python so you can validate the same governance mindset: subscription, resource group, project, Key Vault, and managed identity should be treated as platform prerequisites, not ad hoc setup."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "project_config = {\n",
        "    \"SubscriptionId\": \"00000000-0000-0000-0000-000000000000\",\n",
        "    \"ResourceGroup\": \"rg-foundry-prod\",\n",
        "    \"Location\": \"eastus\",\n",
        "    \"FoundryProject\": \"fdry-enterprise-agents\",\n",
        "    \"KeyVault\": \"kv-foundry-prod\",\n",
        "    \"ManagedIdentity\": \"mi-foundry-agents\",\n",
        "}\n",
        "\n",
        "print(json.dumps(project_config, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## APIM AI gateway policy scaffold in Python\n",
        "\n",
        "The blog positions Azure API Management as a runtime governance layer for models, agents, and tools. This cell recreates the policy scaffold as a Python string so you can inspect the policy elements that matter most: correlation IDs, rate limiting, managed identity auth, and backend routing."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "api_name = \"agents-api\"\n",
        "backend_url = \"https://foundry.contoso.internal\"\n",
        "policy_xml = f\"\"\"\n",
        "<policies>\n",
        "  <inbound>\n",
        "    <base />\n",
        "    <set-header name=\\\"x-correlation-id\\\" exists-action=\\\"override\\\">\n",
        "      <value>@(context.RequestId.ToString())</value>\n",
        "    </set-header>\n",
        "    <rate-limit calls=\\\"60\\\" renewal-period=\\\"60\\\" />\n",
        "    <authentication-managed-identity resource=\\\"https://cognitiveservices.azure.com\\\" />\n",
        "    <set-backend-service base-url=\\\"{backend_url}\\\" />\n",
        "  </inbound>\n",
        "  <backend><base /></backend>\n",
        "  <outbound><base /></outbound>\n",
        "</policies>\n",
        "\"\"\".strip()\n",
        "\n",
        "print(policy_xml)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required variables for production-style validation\n",
        "\n",
        "If you adapt these examples to a real environment, define the following values before deployment or integration testing:\n",
        "\n",
        "- `APIM_NAME`\n",
        "- `FOUNDRY_PROJECT`\n",
        "- `LOG_ANALYTICS_ID`\n",
        "- `PRIVATE_ENDPOINTS`\n",
        "- `AZURE_SUBSCRIPTION_ID`\n",
        "- `RESOURCE_GROUP`\n",
        "- `KEY_VAULT_NAME`\n",
        "- `MANAGED_IDENTITY_NAME`\n",
        "\n",
        "In this notebook, we use mock values only."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate required platform settings\n",
        "\n",
        "This example translates the blog's PowerShell preflight check into Python. It demonstrates a simple but useful control: fail fast if required platform settings are missing before enabling hosted agents in production."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "required = {\n",
        "    \"APIM_NAME\": \"apim-enterprise-prod\",\n",
        "    \"FOUNDRY_PROJECT\": \"fdry-enterprise-agents\",\n",
        "    \"LOG_ANALYTICS_ID\": \"workspace-123\",\n",
        "    \"PRIVATE_ENDPOINTS\": \"enabled\",\n",
        "}\n",
        "\n",
        "for key, value in required.items():\n",
        "    if value is None or str(value).strip() == \"\":\n",
        "        raise ValueError(f\"Missing required setting: {key}\")\n",
        "    print(f\"OK: {key} = {value}\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Simulating a governed long-running run contract\n",
        "\n",
        "The blog recommends standardizing the run contract itself. This example expands the minimal run model with owner metadata, correlation ID, timeout policy, cancellation behavior, and retention policy so you can inspect what a more enterprise-ready run record might look like."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "@dataclass\n",
        "class EnterpriseRun:\n",
        "    id: str\n",
        "    owner: str\n",
        "    state: str\n",
        "    correlation_id: str\n",
        "    tool_call_id: Optional[str]\n",
        "    timeout_seconds: int\n",
        "    cancellation_behavior: str\n",
        "    retention_days: int\n",
        "\n",
        "enterprise_run = EnterpriseRun(\n",
        "    id=\"run_1001\",\n",
        "    owner=\"procurement-ops\",\n",
        "    state=\"requires_action\",\n",
        "    correlation_id=\"corr-abc-123\",\n",
        "    tool_call_id=\"sap-approval-77\",\n",
        "    timeout_seconds=3600,\n",
        "    cancellation_behavior=\"manual_approval_required\",\n",
        "    retention_days=30,\n",
        ")\n",
        "\n",
        "pd.DataFrame([asdict(enterprise_run)])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Classifying slow versus stuck runs\n",
        "\n",
        "One of the blog's operational questions is how teams distinguish a slow run from a stuck one. This simulation creates elapsed times and classifies runs using simple thresholds, which is a useful starting point for dashboards and alerting."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sample_runs = [\n",
        "    {\"run_id\": \"run_1\", \"state\": \"in_progress\", \"elapsed_seconds\": 45, \"tool_call_id\": None},\n",
        "    {\"run_id\": \"run_2\", \"state\": \"requires_action\", \"elapsed_seconds\": 320, \"tool_call_id\": \"mcp_22\"},\n",
        "    {\"run_id\": \"run_3\", \"state\": \"requires_action\", \"elapsed_seconds\": 5400, \"tool_call_id\": \"mcp_99\"},\n",
        "    {\"run_id\": \"run_4\", \"state\": \"completed\", \"elapsed_seconds\": 12, \"tool_call_id\": None},\n",
        "]\n",
        "\n",
        "def classify_run(row: Dict[str, Any]) -> str:\n",
        "    if row[\"state\"] in {\"completed\", \"failed\", \"cancelled\"}:\n",
        "        return \"terminal\"\n",
        "    if row[\"elapsed_seconds\"] > 1800:\n",
        "        return \"stuck\"\n",
        "    if row[\"elapsed_seconds\"] > 120:\n",
        "        return \"slow\"\n",
        "    return \"healthy\"\n",
        "\n",
        "for row in sample_runs:\n",
        "    row[\"classification\"] = classify_run(row)\n",
        "\n",
        "pd.DataFrame(sample_runs)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Tool access boundary inventory\n",
        "\n",
        "The post argues that not every MCP server should be reachable by every agent. This cell models a simple tool onboarding inventory with access mode, long-running support, retry policy, and audit event requirements."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "tools = [\n",
        "    {\n",
        "        \"tool_name\": \"sap.approval.submit\",\n",
        "        \"system\": \"SAP\",\n",
        "        \"access_mode\": \"transaction\",\n",
        "        \"supports_long_running\": True,\n",
        "        \"timeout_seconds\": 900,\n",
        "        \"retry_policy\": \"exponential_backoff\",\n",
        "        \"audit_event\": \"approval_submitted\"\n",
        "    },\n",
        "    {\n",
        "        \"tool_name\": \"sharepoint.read.docs\",\n",
        "        \"system\": \"SharePoint\",\n",
        "        \"access_mode\": \"read_only\",\n",
        "        \"supports_long_running\": False,\n",
        "        \"timeout_seconds\": 30,\n",
        "        \"retry_policy\": \"standard\",\n",
        "        \"audit_event\": \"documents_read\"\n",
        "    },\n",
        "    {\n",
        "        \"tool_name\": \"servicenow.ticket.create\",\n",
        "        \"system\": \"ServiceNow\",\n",
        "        \"access_mode\": \"write\",\n",
        "        \"supports_long_running\": True,\n",
        "        \"timeout_seconds\": 300,\n",
        "        \"retry_policy\": \"idempotent_retry\",\n",
        "        \"audit_event\": \"ticket_created\"\n",
        "    }\n",
        "]\n",
        "\n",
        "pd.DataFrame(tools)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Approval checkpoint policy\n",
        "\n",
        "The blog draws a line between broad analysis and bounded execution. This example encodes a simple approval policy so you can test which actions should require human approval before a run resumes."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "actions = [\n",
        "    {\"action\": \"summarize_contract\", \"category\": \"analysis\"},\n",
        "    {\"action\": \"submit_sap_approval\", \"category\": \"execution\"},\n",
        "    {\"action\": \"create_ticket\", \"category\": \"execution\"},\n",
        "    {\"action\": \"draft_email\", \"category\": \"analysis\"},\n",
        "]\n",
        "\n",
        "def requires_approval(action_category: str) -> bool:\n",
        "    return action_category == \"execution\"\n",
        "\n",
        "results = []\n",
        "for item in actions:\n",
        "    results.append({\n",
        "        **item,\n",
        "        \"approval_required\": requires_approval(item[\"category\"])\n",
        "    })\n",
        "\n",
        "pd.DataFrame(results)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Cost controls for durable agent workloads\n",
        "\n",
        "The post notes that model calls are only part of the bill. This example estimates total run cost across model usage, tool invocations, retries, and persisted state time to reinforce the idea that agent spend should be treated as a platform budget."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "cost_inputs = {\n",
        "    \"model_calls\": 12,\n",
        "    \"model_cost_per_call\": 0.015,\n",
        "    \"tool_invocations\": 4,\n",
        "    \"tool_cost_per_call\": 0.02,\n",
        "    \"retries\": 2,\n",
        "    \"retry_cost_each\": 0.01,\n",
        "    \"persisted_state_hours\": 3,\n",
        "    \"state_cost_per_hour\": 0.005,\n",
        "}\n",
        "\n",
        "total_cost = (\n",
        "    cost_inputs[\"model_calls\"] * cost_inputs[\"model_cost_per_call\"]\n",
        "    + cost_inputs[\"tool_invocations\"] * cost_inputs[\"tool_cost_per_call\"]\n",
        "    + cost_inputs[\"retries\"] * cost_inputs[\"retry_cost_each\"]\n",
        "    + cost_inputs[\"persisted_state_hours\"] * cost_inputs[\"state_cost_per_hour\"]\n",
        ")\n",
        "\n",
        "cost_breakdown = pd.DataFrame([\n",
        "    {\"component\": \"model_calls\", \"cost\": cost_inputs[\"model_calls\"] * cost_inputs[\"model_cost_per_call\"]},\n",
        "    {\"component\": \"tool_invocations\", \"cost\": cost_inputs[\"tool_invocations\"] * cost_inputs[\"tool_cost_per_call\"]},\n",
        "    {\"component\": \"retries\", \"cost\": cost_inputs[\"retries\"] * cost_inputs[\"retry_cost_each\"]},\n",
        "    {\"component\": \"persisted_state\", \"cost\": cost_inputs[\"persisted_state_hours\"] * cost_inputs[\"state_cost_per_hour\"]},\n",
        "    {\"component\": \"total\", \"cost\": total_cost},\n",
        "])\n",
        "\n",
        "cost_breakdown"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Grounding and organizational context inventory\n",
        "\n",
        "The blog argues that governed grounding matters more than model horse-race debates. This cell models a simple inventory of context sources with freshness, permission sensitivity, and provenance so you can reason about enterprise grounding quality."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "grounding_sources = [\n",
        "    {\n",
        "        \"source\": \"documents\",\n",
        "        \"freshness\": \"daily\",\n",
        "        \"permission_aware\": True,\n",
        "        \"provenance_available\": True\n",
        "    },\n",
        "    {\n",
        "        \"source\": \"meetings\",\n",
        "        \"freshness\": \"near_real_time\",\n",
        "        \"permission_aware\": True,\n",
        "        \"provenance_available\": True\n",
        "    },\n",
        "    {\n",
        "        \"source\": \"chats\",\n",
        "        \"freshness\": \"near_real_time\",\n",
        "        \"permission_aware\": True,\n",
        "        \"provenance_available\": True\n",
        "    },\n",
        "    {\n",
        "        \"source\": \"workflows\",\n",
        "        \"freshness\": \"hourly\",\n",
        "        \"permission_aware\": True,\n",
        "        \"provenance_available\": False\n",
        "    }\n",
        "]\n",
        "\n",
        "pd.DataFrame(grounding_sources)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference architecture checklist\n",
        "\n",
        "The blog recommends defining a reference architecture before business units scatter agents across projects and subscriptions. This final validation cell turns that recommendation into a checklist you can review or adapt for your own platform standards."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "reference_architecture_checklist = [\n",
        "    \"Identity boundaries defined\",\n",
        "    \"Gateway placement standardized\",\n",
        "    \"Run telemetry schema approved\",\n",
        "    \"Tool onboarding process documented\",\n",
        "    \"Approval checkpoints defined\",\n",
        "    \"Timeout and cancellation policies set\",\n",
        "    \"Retention policy documented\",\n",
        "    \"Cost reporting and showback enabled\",\n",
        "    \"Incident response ownership assigned\",\n",
        "    \"Subscription and project boundaries standardized\",\n",
        "]\n",
        "\n",
        "checklist_df = pd.DataFrame({\n",
        "    \"check\": reference_architecture_checklist,\n",
        "    \"status\": [False] * len(reference_architecture_checklist)\n",
        "})\n",
        "checklist_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Next Steps\n",
        "\n",
        "This notebook validated the blog's main claim: enterprise agents should be treated as durable workloads with identity, state, approvals, telemetry, and cost controls. The most important design object is the run lifecycle, especially the point where a long-running tool pauses execution and later resumes under policy.\n",
        "\n",
        "Suggested next steps:\n",
        "- Pilot one narrow long-running use case such as approval routing or service triage.\n",
        "- Standardize a run contract with IDs, ownership, state transitions, and retention.\n",
        "- Put APIM or an equivalent gateway in the runtime path.\n",
        "- Define tool onboarding, approval, and observability baselines before scaling agent adoption.\n",
        "- Measure not just model quality, but operational quality: stuck runs, retry rates, approval latency, and total cost."
      ]
    }
  ]
}