{
  "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": "Long-Running Agents in Foundry Agent Service: The Enterprise Case for Durable AI Workflows",
      "slug": "long-running-agents-in-foundry-agent-service-the-enterprise-",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-07-15T17:50:54.564Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Long-Running Agents in Foundry Agent Service: The Enterprise Case for Durable AI Workflows\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation guide focused on durable AI workflows, approval checkpoints, persistence, auditability, and enterprise readiness. The goal is to help you distinguish between short-lived assistants and truly durable agent workflows that can survive waits, failures, and resumptions across systems."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install -q pandas"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from dataclasses import dataclass, field\n",
        "from datetime import datetime, timedelta\n",
        "from pathlib import Path\n",
        "import json\n",
        "import time\n",
        "import uuid\n",
        "import pandas as pd"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Durable workflow mental model\n",
        "\n",
        "A durable agent workflow is justified when work spans time, systems, uncertainty, and resumability requirements. The following cell encodes the blog's flowchart as a simple adjacency list so you can inspect the workflow structure programmatically."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "workflow_graph = {\n",
        "    'Business event arrives': ['Start durable agent run'],\n",
        "    'Start durable agent run': ['Gather context and draft plan'],\n",
        "    'Gather context and draft plan': ['Needs human approval?'],\n",
        "    'Needs human approval?': ['Execute tool/action', 'Persist state and wait'],\n",
        "    'Persist state and wait': ['Approval event or timeout'],\n",
        "    'Approval event or timeout': ['Approved?'],\n",
        "    'Approved?': ['Execute tool/action', 'Close run with rejection outcome'],\n",
        "    'Execute tool/action': ['Persist result and audit trail'],\n",
        "    'Persist result and audit trail': ['Resume later steps if needed']\n",
        "}\n",
        "\n",
        "for step, next_steps in workflow_graph.items():\n",
        "    print(f'{step} -> {next_steps}')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Minimal durable agent workflow with an approval checkpoint\n",
        "\n",
        "This example demonstrates the core idea of durability: a run has identity, state, and history before and after a pause. The `sleep` call is only a stand-in for a platform-managed wait; the important part is that the run can resume with intact context."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "# Minimal durable agent workflow with an approval checkpoint\n",
        "from dataclasses import dataclass, field\n",
        "from datetime import datetime\n",
        "import time\n",
        "\n",
        "@dataclass\n",
        "class DurableRun:\n",
        "    run_id: str\n",
        "    state: str = 'draft'\n",
        "    history: list[str] = field(default_factory=list)\n",
        "\n",
        "run = DurableRun(run_id='run-1001')\n",
        "run.history.append(f\"{datetime.utcnow().isoformat()} created\")\n",
        "run.state = 'waiting_for_approval'\n",
        "run.history.append(f\"{datetime.utcnow().isoformat()} paused for approval\")\n",
        "\n",
        "time.sleep(1)  # stand-in for a long wait handled by the platform\n",
        "\n",
        "approval_received = True\n",
        "run.state = 'approved' if approval_received else 'rejected'\n",
        "run.history.append(f\"{datetime.utcnow().isoformat()} {run.state}\")\n",
        "print(run)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Persist and resume workflow state across long-running steps\n",
        "\n",
        "This example validates a simple persistence pattern using a local JSON file. In enterprise platforms, this state would typically live in a durable store, but the notebook version makes the resume behavior easy to test."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "# Persist and resume durable workflow state across long-running steps\n",
        "import json\n",
        "from pathlib import Path\n",
        "\n",
        "state_file = Path('agent_run_state.json')\n",
        "state = {\n",
        "    'run_id': 'run-2001',\n",
        "    'step': 'await_vendor_response',\n",
        "    'status': 'suspended',\n",
        "    'context': {'ticket': 'INC-4821', 'owner': 'ops-team'}\n",
        "}\n",
        "\n",
        "state_file.write_text(json.dumps(state, indent=2))\n",
        "loaded = json.loads(state_file.read_text())\n",
        "loaded['status'] = 'resumed'\n",
        "loaded['step'] = 'finalize_recommendation'\n",
        "print(json.dumps(loaded, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Timeout-aware wait pattern for escalation logic\n",
        "\n",
        "Timeouts are business outcomes, not just technical exceptions. This example shows how a delayed approval can trigger escalation instead of normal continuation."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "# Timeout-aware wait pattern for enterprise escalation logic\n",
        "from datetime import datetime, timedelta\n",
        "\n",
        "started = datetime.utcnow()\n",
        "deadline = started + timedelta(minutes=30)\n",
        "approval_event_time = started + timedelta(minutes=45)\n",
        "\n",
        "if approval_event_time <= deadline:\n",
        "    outcome = 'approved_in_time'\n",
        "else:\n",
        "    outcome = 'timed_out_escalate'\n",
        "\n",
        "result = {\n",
        "    'started': started.isoformat(),\n",
        "    'deadline': deadline.isoformat(),\n",
        "    'approval_event_time': approval_event_time.isoformat(),\n",
        "    'outcome': outcome,\n",
        "}\n",
        "print(result)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Sequence model of app, agent, state store, and human approval\n",
        "\n",
        "The blog emphasizes that the durable state store is the backbone of resumability and auditability. The next cell represents the sequence diagram as an ordered event list you can inspect or extend."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sequence = [\n",
        "    ('Business App', 'Foundry Agent Service', 'Start run with task context'),\n",
        "    ('Foundry Agent Service', 'Durable State Store', 'Save workflow state'),\n",
        "    ('Foundry Agent Service', 'Approver', 'Request approval'),\n",
        "    ('Foundry Agent Service', 'Durable State Store', 'Suspend run'),\n",
        "    ('Approver', 'Foundry Agent Service', 'Approve or reject later'),\n",
        "    ('Foundry Agent Service', 'Durable State Store', 'Load saved state'),\n",
        "    ('Foundry Agent Service', 'Business App', 'Resume execution and return outcome')\n",
        "]\n",
        "\n",
        "for sender, receiver, action in sequence:\n",
        "    print(f'{sender} -> {receiver}: {action}')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Azure inspection example translated from PowerShell to Python\n",
        "\n",
        "The original post included PowerShell for inspecting Azure resources. To keep this notebook fully Python-based, the next cell uses mock data to simulate the kind of inventory review a platform team would perform before rollout."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "resources = [\n",
        "    {'Name': 'foundry-prod', 'ResourceType': 'Microsoft.CognitiveServices/accounts', 'Location': 'eastus'},\n",
        "    {'Name': 'ml-prod', 'ResourceType': 'Microsoft.MachineLearningServices/workspaces', 'Location': 'eastus'},\n",
        "    {'Name': 'stfoundryprod001', 'ResourceType': 'Microsoft.Storage/storageAccounts', 'Location': 'eastus'},\n",
        "    {'Name': 'kv-foundry-prod', 'ResourceType': 'Microsoft.KeyVault/vaults', 'Location': 'eastus'}\n",
        "]\n",
        "\n",
        "df_resources = pd.DataFrame(resources)\n",
        "print('All resources:')\n",
        "print(df_resources)\n",
        "\n",
        "interesting = df_resources[df_resources['ResourceType'].str.contains('CognitiveServices|MachineLearningServices|Storage|KeyVault', regex=True)]\n",
        "print('\\nInteresting resources:')\n",
        "print(interesting.to_json(orient='records', indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Review rollout configuration surfaces\n",
        "\n",
        "This notebook version simulates the configuration checks that matter for enterprise agent deployments, such as storage posture and key vault settings. These checks help validate whether the surrounding platform is ready for durable workflows."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "config_review = {\n",
        "    'StorageSku': 'Standard_LRS',\n",
        "    'StorageKind': 'StorageV2',\n",
        "    'AllowBlobPublicAccess': False,\n",
        "    'KeyVaultEnabledForDeployment': False,\n",
        "    'SoftDeleteRetentionInDays': 90\n",
        "}\n",
        "\n",
        "for k, v in config_review.items():\n",
        "    print(f'{k}: {v}')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Diagnostic settings and observability readiness\n",
        "\n",
        "Observability is a production gate for long-running agents. The next cell simulates a diagnostic settings check and highlights the blog's point that missing diagnostics should stop rollout discussions."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "diagnostic_settings = {\n",
        "    'Name': 'send-to-log-analytics',\n",
        "    'WorkspaceId': '/subscriptions/000/resourceGroups/rg-monitor/providers/Microsoft.OperationalInsights/workspaces/law-prod',\n",
        "    'EventHubAuthorizationRuleId': None,\n",
        "    'StorageAccountId': '/subscriptions/000/resourceGroups/rg-foundry-prod/providers/Microsoft.Storage/storageAccounts/stfoundryprod001'\n",
        "}\n",
        "\n",
        "if diagnostic_settings:\n",
        "    print(json.dumps(diagnostic_settings, indent=2))\n",
        "else:\n",
        "    print('No diagnostic settings found for resource.')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Emit an auditable event record for each workflow transition\n",
        "\n",
        "Audit trails are essential for regulated or revenue-impacting processes. This example creates a structured event record with actor, transition, timestamp, and correlation ID."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "# Emit an auditable event record for each durable workflow transition\n",
        "from datetime import datetime\n",
        "import json\n",
        "\n",
        "event = {\n",
        "    'run_id': 'run-3007',\n",
        "    'agent': 'procurement-review-agent',\n",
        "    'transition': 'waiting_for_approval -> approved',\n",
        "    'actor': 'manager@contoso.com',\n",
        "    'timestamp_utc': datetime.utcnow().isoformat(),\n",
        "    'correlation_id': '8f7f2d6a-1d7d-4d6f-a0d8-2d9b8f6f1a11'\n",
        "}\n",
        "\n",
        "print(json.dumps(event, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate idempotency for side-effecting actions\n",
        "\n",
        "The blog argues that durability without idempotency leads to duplicate tickets, orders, or notifications. This example simulates a repeat-safe action handler using an idempotency key."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "processed_actions = {}\n",
        "\n",
        "def create_ticket_once(idempotency_key: str, payload: dict):\n",
        "    if idempotency_key in processed_actions:\n",
        "        return {\n",
        "            'status': 'duplicate_ignored',\n",
        "            'ticket_id': processed_actions[idempotency_key]['ticket_id'],\n",
        "            'payload': payload\n",
        "        }\n",
        "    ticket_id = f\"TCK-{len(processed_actions) + 1000}\"\n",
        "    processed_actions[idempotency_key] = {\n",
        "        'ticket_id': ticket_id,\n",
        "        'created_at': datetime.utcnow().isoformat(),\n",
        "        'payload': payload\n",
        "    }\n",
        "    return {\n",
        "        'status': 'created',\n",
        "        'ticket_id': ticket_id,\n",
        "        'payload': payload\n",
        "    }\n",
        "\n",
        "payload = {'vendor': 'Contoso', 'request_type': 'procurement_review'}\n",
        "key = 'req-abc-123'\n",
        "print(create_ticket_once(key, payload))\n",
        "print(create_ticket_once(key, payload))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Compare good fits and bad fits for durable agents\n",
        "\n",
        "The blog recommends using durable agents only when persistence, cross-system coordination, uncertainty, and resumability are all required. The next cell turns those examples into a simple evaluation table."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "use_cases = [\n",
        "    {'use_case': 'Exception handling across CRM, ERP, and ticketing', 'fit': 'good'},\n",
        "    {'use_case': 'Procurement or finance approvals with document review', 'fit': 'good'},\n",
        "    {'use_case': 'Incident triage that waits on vendor input', 'fit': 'good'},\n",
        "    {'use_case': 'Claims or case workflows that need human checkpoints', 'fit': 'good'},\n",
        "    {'use_case': 'Q&A over a knowledge base', 'fit': 'bad'},\n",
        "    {'use_case': 'Summarization', 'fit': 'bad'},\n",
        "    {'use_case': 'Single-shot document extraction', 'fit': 'bad'},\n",
        "    {'use_case': 'Deterministic automation already modeled in Logic Apps or Functions', 'fit': 'bad'}\n",
        "]\n",
        "\n",
        "df_use_cases = pd.DataFrame(use_cases)\n",
        "print(df_use_cases)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Team readiness self-assessment\n",
        "\n",
        "The post ends with a challenge: rate your team's readiness from 1 to 5. This cell provides a lightweight rubric across persisted state, retries, approvals, audit trails, observability, and ownership."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "readiness = {\n",
        "    'persisted_state': 1,\n",
        "    'retries_and_idempotency': 1,\n",
        "    'approval_checkpoints': 1,\n",
        "    'audit_trails': 1,\n",
        "    'observability': 1,\n",
        "    'clear_ownership': 1\n",
        "}\n",
        "\n",
        "average_score = sum(readiness.values()) / len(readiness)\n",
        "summary = {\n",
        "    'category_scores': readiness,\n",
        "    'average_score': round(average_score, 2),\n",
        "    'interpretation': (\n",
        "        'Not ready' if average_score < 2 else\n",
        "        'Early stage' if average_score < 3 else\n",
        "        'Moderately ready' if average_score < 4 else\n",
        "        'Strong readiness'\n",
        "    )\n",
        "}\n",
        "\n",
        "print(json.dumps(summary, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Next Steps\n",
        "\n",
        "This notebook validated the blog's central claim: durable agents are valuable when work persists across time, systems, failures, and approvals, not when a simple assistant or deterministic workflow would do. The practical enterprise case is about orchestration, state, identity boundaries, observability, idempotency, and auditability.\n",
        "\n",
        "- Pick one narrow workflow with a real approval or wait state.\n",
        "- Keep deterministic steps in Logic Apps, Functions, or existing workflow tooling.\n",
        "- Define governance, retry limits, logging standards, and ownership before scaling.\n",
        "- Use your readiness score to identify the operational gaps that must be closed before production."
      ]
    }
  ]
}