{
  "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:21:48.583Z"
    }
  },
  "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 workflow focused on long-running agent execution, task references, polling, resumability, governance, and auditability. The goal is not to call Microsoft services directly, but to simulate the runtime and control-plane patterns the post argues matter most in enterprise settings."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install -q requests pydantic"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import json\n",
        "import time\n",
        "import uuid\n",
        "from pathlib import Path\n",
        "from datetime import datetime, timezone\n",
        "from typing import Dict, Any, List"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Architecture pattern for long-running agent work\n",
        "\n",
        "The blog's core claim is that once a tool returns a task reference instead of an immediate answer, you are operating a distributed system. This cell renders the flow as text so you can validate the control-plane steps in a notebook."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "architecture_flow = {\n",
        "    \"Client App / Copilot\": [\"Microsoft Foundry Agent\"],\n",
        "    \"Microsoft Foundry Agent\": [\"Fast inline tool result\", \"Long-running MCP task reference\"],\n",
        "    \"Long-running MCP task reference\": [\"Persist task id + correlation id\"],\n",
        "    \"Persist task id + correlation id\": [\"Poll task status / inspect run state\"],\n",
        "    \"Poll task status / inspect run state\": [\"Completed?\"],\n",
        "    \"Completed?\": [\"No -> Poll again\", \"Yes -> Fetch final tool output\"],\n",
        "    \"Fetch final tool output\": [\"Apply governance, logging, and audit\"],\n",
        "    \"Apply governance, logging, and audit\": [\"Return enterprise-safe response\"]\n",
        "}\n",
        "\n",
        "for node, edges in architecture_flow.items():\n",
        "    print(f\"{node}\")\n",
        "    for edge in edges:\n",
        "        print(f\"  -> {edge}\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Minimal long-running MCP-style tool invocation\n",
        "\n",
        "This example simulates a Foundry-style client that returns tracking metadata first, not the business result. The important validation is that `run_id` and `task_id` become first-class workflow artifacts."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "class FoundryClient:\n",
        "    def invoke_tool(self, agent_id, tool_name, arguments):\n",
        "        return {\n",
        "            \"run_id\": \"run-123\",\n",
        "            \"task\": {\"id\": \"task-789\", \"status\": \"queued\"},\n",
        "            \"tool_name\": tool_name,\n",
        "            \"arguments\": arguments,\n",
        "        }\n",
        "\n",
        "    def get_task(self, run_id, task_id):\n",
        "        return {\n",
        "            \"id\": task_id,\n",
        "            \"status\": \"succeeded\",\n",
        "            \"output\": {\"summary\": \"Indexed 42 files\"},\n",
        "        }\n",
        "\n",
        "client = FoundryClient()\n",
        "agent_id = \"agent-enterprise-ops\"\n",
        "\n",
        "result = client.invoke_tool(\n",
        "    agent_id=agent_id,\n",
        "    tool_name=\"mcp.sharepoint.index_site\",\n",
        "    arguments={\"siteUrl\": \"https://contoso.sharepoint.com/sites/legal\"},\n",
        ")\n",
        "\n",
        "run_id = result[\"run_id\"]\n",
        "task_id = result[\"task\"][\"id\"]\n",
        "print(f\"Run={run_id} Task={task_id}\")\n",
        "print(json.dumps(result, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Polling and terminal-state handling\n",
        "\n",
        "Long-running execution requires a polling loop with explicit terminal states such as `succeeded`, `failed`, and `cancelled`. This simulation walks through queued and running states before completion so you can validate timeout and state-transition behavior."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def poll_task(client, run_id, task_id, interval_seconds=0.2, timeout_seconds=5):\n",
        "    deadline = time.time() + timeout_seconds\n",
        "    while time.time() < deadline:\n",
        "        task = client.get_task(run_id, task_id)\n",
        "        state = task[\"status\"]\n",
        "        print(f\"task={task_id} state={state}\")\n",
        "        if state in {\"succeeded\", \"failed\", \"cancelled\"}:\n",
        "            return task\n",
        "        time.sleep(interval_seconds)\n",
        "    raise TimeoutError(f\"Task {task_id} did not finish before timeout\")\n",
        "\n",
        "class SimulatedFoundryClient:\n",
        "    def __init__(self):\n",
        "        self.calls = 0\n",
        "\n",
        "    def get_task(self, run_id, task_id):\n",
        "        self.calls += 1\n",
        "        if self.calls == 1:\n",
        "            return {\"id\": task_id, \"status\": \"queued\"}\n",
        "        if self.calls == 2:\n",
        "            return {\"id\": task_id, \"status\": \"running\"}\n",
        "        return {\"id\": task_id, \"status\": \"succeeded\", \"output\": {\"summary\": \"Done\"}}\n",
        "\n",
        "final_task = poll_task(SimulatedFoundryClient(), \"run-123\", \"task-789\")\n",
        "print(\"Final output:\", final_task.get(\"output\", {}).get(\"summary\"))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Persist task references for resumable workflows\n",
        "\n",
        "A key enterprise pattern is persisting task metadata so work can resume after notebook restarts, service restarts, or operator handoffs. This example writes a task reference to disk and reloads it for later reconciliation."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "task_ref = {\n",
        "    \"agent_id\": \"agent-enterprise-ops\",\n",
        "    \"run_id\": \"run-123\",\n",
        "    \"task_id\": \"task-789\",\n",
        "    \"correlation_id\": \"corr-456\",\n",
        "    \"requested_by\": \"finance-analyst@contoso.com\",\n",
        "}\n",
        "\n",
        "path = Path(\"task_ref.json\")\n",
        "path.write_text(json.dumps(task_ref, indent=2), encoding=\"utf-8\")\n",
        "\n",
        "loaded = json.loads(path.read_text(encoding=\"utf-8\"))\n",
        "print(f\"Resume polling for run={loaded['run_id']} task={loaded['task_id']}\")\n",
        "print(json.dumps(loaded, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Sequence view of governed long-running execution\n",
        "\n",
        "The blog emphasizes that governance sits between the agent runtime and enterprise tools. This cell prints the sequence of interactions among user, agent, API gateway, and backend to make the control points explicit."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sequence_steps = [\n",
        "    (\"User/App\", \"Foundry Agent\", \"Ask for enterprise action\"),\n",
        "    (\"Foundry Agent\", \"APIM AI Gateway\", \"Tool request with policy context\"),\n",
        "    (\"APIM AI Gateway\", \"MCP Tool Backend\", \"Forward governed request\"),\n",
        "    (\"MCP Tool Backend\", \"Foundry Agent\", \"taskRef(taskId, status=queued)\"),\n",
        "    (\"Foundry Agent\", \"User/App\", \"Accepted + tracking metadata\"),\n",
        "    (\"Foundry Agent\", \"APIM AI Gateway\", \"GET task status\"),\n",
        "    (\"APIM AI Gateway\", \"MCP Tool Backend\", \"Check long-running job\"),\n",
        "    (\"MCP Tool Backend\", \"Foundry Agent\", \"running / succeeded\"),\n",
        "    (\"Foundry Agent\", \"User/App\", \"Final result with audit trail\"),\n",
        "]\n",
        "\n",
        "for src, dst, msg in sequence_steps:\n",
        "    print(f\"{src} -> {dst}: {msg}\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required variables for real API gateway integration\n",
        "\n",
        "The original post includes PowerShell examples for Azure API Management. If you adapt this notebook to real infrastructure, you would typically need values such as:\n",
        "\n",
        "- `AZURE_SUBSCRIPTION_ID`\n",
        "- `AZURE_RESOURCE_GROUP`\n",
        "- `APIM_NAME`\n",
        "- `APIM_API_ID`\n",
        "- `BACKEND_URL`\n",
        "- `BACKEND_API_KEY`\n",
        "\n",
        "The notebook below keeps everything local and simulated."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Simulate APIM backend registration and named values in Python\n",
        "\n",
        "The blog uses PowerShell to define a governed backend and secret storage. This Python version models the same idea: centralized backend definition plus secret indirection as a control-plane primitive."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "apim_config = {\n",
        "    \"resource_group\": \"rg-foundry\",\n",
        "    \"apim_name\": \"apim-contoso\",\n",
        "    \"named_values\": {\n",
        "        \"agent-backend-key\": \"replace-me\"\n",
        "    },\n",
        "    \"backends\": {\n",
        "        \"foundry-agent-backend\": {\n",
        "            \"url\": \"https://agent-backend.contoso.internal\",\n",
        "            \"protocol\": \"http\",\n",
        "            \"title\": \"Foundry Agent Backend\",\n",
        "            \"description\": \"Governed backend for long-running MCP tools\",\n",
        "        }\n",
        "    },\n",
        "}\n",
        "\n",
        "print(\"Named values:\")\n",
        "print(json.dumps(apim_config[\"named_values\"], indent=2))\n",
        "print(\"\\nBackends:\")\n",
        "print(json.dumps(apim_config[\"backends\"], indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Simulate APIM policy for correlation, auth, and timeout\n",
        "\n",
        "This Python example mirrors the PowerShell policy snippet by stamping a correlation ID, injecting authorization, and enforcing a timeout envelope. It demonstrates how governance can be applied consistently before traffic reaches a tool backend."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def apply_gateway_policy(request: Dict[str, Any], named_values: Dict[str, str], timeout_seconds: int = 120) -> Dict[str, Any]:\n",
        "    governed = dict(request)\n",
        "    headers = dict(governed.get(\"headers\", {}))\n",
        "    headers[\"x-correlation-id\"] = str(uuid.uuid4())\n",
        "    headers[\"Authorization\"] = named_values[\"agent-backend-key\"]\n",
        "    governed[\"headers\"] = headers\n",
        "    governed[\"timeout_seconds\"] = timeout_seconds\n",
        "    return governed\n",
        "\n",
        "request = {\n",
        "    \"method\": \"POST\",\n",
        "    \"url\": \"/tools/mcp.sharepoint.index_site\",\n",
        "    \"headers\": {},\n",
        "    \"body\": {\"siteUrl\": \"https://contoso.sharepoint.com/sites/legal\"},\n",
        "}\n",
        "\n",
        "governed_request = apply_gateway_policy(request, apim_config[\"named_values\"], timeout_seconds=120)\n",
        "print(json.dumps(governed_request, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Normalize run states into operator-friendly categories\n",
        "\n",
        "The post recommends collapsing model-native states into categories that security and SRE teams can reason about quickly. This mapping is useful for dashboards, alerts, and incident triage."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def classify_run_state(task):\n",
        "    state = task.get(\"status\", \"unknown\")\n",
        "    if state in {\"queued\", \"running\"}:\n",
        "        return \"InProgress\"\n",
        "    if state == \"succeeded\":\n",
        "        return \"Healthy\"\n",
        "    if state == \"failed\":\n",
        "        return \"ActionRequired\"\n",
        "    if state == \"cancelled\":\n",
        "        return \"Stopped\"\n",
        "    return \"Unknown\"\n",
        "\n",
        "samples = [\n",
        "    {\"status\": \"queued\"},\n",
        "    {\"status\": \"running\"},\n",
        "    {\"status\": \"succeeded\"},\n",
        "    {\"status\": \"failed\"},\n",
        "    {\"status\": \"cancelled\"},\n",
        "    {\"status\": \"mystery\"},\n",
        "]\n",
        "\n",
        "for task in samples:\n",
        "    print(task[\"status\"], \"=>\", classify_run_state(task))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Structured audit logging for long-running agent work\n",
        "\n",
        "A minimum viable audit trail includes acceptance, polling, and completion events. This example emits JSON records that can later feed dashboards, SIEM pipelines, or incident reviews."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def audit(event_type, payload):\n",
        "    record = {\n",
        "        \"ts\": datetime.now(timezone.utc).isoformat(),\n",
        "        \"event\": event_type,\n",
        "        \"payload\": payload,\n",
        "    }\n",
        "    print(json.dumps(record))\n",
        "\n",
        "audit(\"tool.accepted\", {\"run_id\": \"run-123\", \"task_id\": \"task-789\"})\n",
        "audit(\"tool.polled\", {\"task_id\": \"task-789\", \"status\": \"running\"})\n",
        "audit(\"tool.completed\", {\"task_id\": \"task-789\", \"status\": \"succeeded\", \"duration_s\": 18})"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## End-to-end simulation: invoke, persist, poll, classify, and audit\n",
        "\n",
        "This final hands-on example combines the notebook's patterns into one enterprise-style flow. It demonstrates how a long-running tool call can be tracked from acceptance through completion with persisted state, governance metadata, operator-friendly status, and audit events."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "class EnterpriseFoundryClient:\n",
        "    def __init__(self):\n",
        "        self.task_states = {}\n",
        "        self.poll_counts = {}\n",
        "\n",
        "    def invoke_tool(self, agent_id, tool_name, arguments):\n",
        "        run_id = f\"run-{uuid.uuid4().hex[:8]}\"\n",
        "        task_id = f\"task-{uuid.uuid4().hex[:8]}\"\n",
        "        self.task_states[(run_id, task_id)] = [\"queued\", \"running\", \"succeeded\"]\n",
        "        self.poll_counts[(run_id, task_id)] = 0\n",
        "        return {\n",
        "            \"run_id\": run_id,\n",
        "            \"task\": {\"id\": task_id, \"status\": \"queued\"},\n",
        "            \"tool_name\": tool_name,\n",
        "            \"arguments\": arguments,\n",
        "        }\n",
        "\n",
        "    def get_task(self, run_id, task_id):\n",
        "        key = (run_id, task_id)\n",
        "        idx = self.poll_counts[key]\n",
        "        states = self.task_states[key]\n",
        "        state = states[min(idx, len(states) - 1)]\n",
        "        self.poll_counts[key] += 1\n",
        "        payload = {\"id\": task_id, \"status\": state}\n",
        "        if state == \"succeeded\":\n",
        "            payload[\"output\"] = {\"summary\": \"Indexed 42 files\", \"documents\": 42}\n",
        "        return payload\n",
        "\n",
        "client = EnterpriseFoundryClient()\n",
        "request = {\n",
        "    \"method\": \"POST\",\n",
        "    \"url\": \"/tools/mcp.sharepoint.index_site\",\n",
        "    \"headers\": {},\n",
        "    \"body\": {\"siteUrl\": \"https://contoso.sharepoint.com/sites/legal\"},\n",
        "}\n",
        "\n",
        "governed_request = apply_gateway_policy(request, apim_config[\"named_values\"], timeout_seconds=120)\n",
        "correlation_id = governed_request[\"headers\"][\"x-correlation-id\"]\n",
        "\n",
        "result = client.invoke_tool(\n",
        "    agent_id=\"agent-enterprise-ops\",\n",
        "    tool_name=\"mcp.sharepoint.index_site\",\n",
        "    arguments=governed_request[\"body\"],\n",
        ")\n",
        "\n",
        "run_id = result[\"run_id\"]\n",
        "task_id = result[\"task\"][\"id\"]\n",
        "\n",
        "audit(\"tool.accepted\", {\n",
        "    \"run_id\": run_id,\n",
        "    \"task_id\": task_id,\n",
        "    \"correlation_id\": correlation_id,\n",
        "    \"tool\": result[\"tool_name\"],\n",
        "})\n",
        "\n",
        "persisted = {\n",
        "    \"agent_id\": \"agent-enterprise-ops\",\n",
        "    \"run_id\": run_id,\n",
        "    \"task_id\": task_id,\n",
        "    \"correlation_id\": correlation_id,\n",
        "    \"requested_by\": \"finance-analyst@contoso.com\",\n",
        "}\n",
        "Path(\"task_ref_end_to_end.json\").write_text(json.dumps(persisted, indent=2), encoding=\"utf-8\")\n",
        "\n",
        "while True:\n",
        "    task = client.get_task(run_id, task_id)\n",
        "    audit(\"tool.polled\", {\n",
        "        \"run_id\": run_id,\n",
        "        \"task_id\": task_id,\n",
        "        \"status\": task[\"status\"],\n",
        "        \"operator_status\": classify_run_state(task),\n",
        "    })\n",
        "    if task[\"status\"] in {\"succeeded\", \"failed\", \"cancelled\"}:\n",
        "        break\n",
        "    time.sleep(0.2)\n",
        "\n",
        "audit(\"tool.completed\", {\n",
        "    \"run_id\": run_id,\n",
        "    \"task_id\": task_id,\n",
        "    \"status\": task[\"status\"],\n",
        "    \"operator_status\": classify_run_state(task),\n",
        "    \"output\": task.get(\"output\", {}),\n",
        "})\n",
        "\n",
        "print(\"\\nFinal task record:\")\n",
        "print(json.dumps(task, indent=2))\n",
        "print(\"\\nPersisted task reference:\")\n",
        "print(Path(\"task_ref_end_to_end.json\").read_text(encoding=\"utf-8\"))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "The blog's main argument holds up in practice: the hard part of enterprise agents is not chat quality, but the operating envelope around long-running work. Once tools return task references, you need persisted state, polling, timeout strategy, governance, identity, and auditability.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "- Replace the simulated client with a real SDK or service wrapper.\n",
        "- Send audit events to a log sink instead of printing them.\n",
        "- Add retry, cancellation, and approval checkpoints.\n",
        "- Externalize task state to durable storage such as a database or queue.\n",
        "- Define SRE dashboards around normalized statuses like `InProgress`, `Healthy`, and `ActionRequired`."
      ]
    }
  ]
}