Last month, our team spent an entire Saturday manually triaging a cascading failure across three Azure regions. We had alerts flooding in from Azure Monitor, scattered runbooks in a wiki nobody had updated in six months, and three engineers stepping on each other's toes trying to figure out who was restarting which service. By the time we stabilized everything, I was exhausted and frustrated. There had to be a better way to automate the tedious parts of incident response—the log scraping, the status checking, the runbook execution—while keeping humans in the loop for the critical decisions.
That weekend, I started experimenting with Microsoft Agent Framework. I'd seen the announcement about their "Agentic DevOps" push and was curious if their new open-source SDK could actually handle real infrastructure tasks, or if it was just another demo-friendly toy. Here's what I learned building an incident mitigation agent from scratch.
Getting Started: The First Agent
Microsoft Agent Framework (MAF) supports .NET, Python, and Go. I went with Python since our DevOps scripts are mostly Python already. The framework is in preview, so you'll need the pre-release packages.
pip install microsoft-agents-core --pre
pip install microsoft-agents-ai-foundry --pre
My first surprise: the framework expects you to bring your own LLM endpoint. It supports Azure OpenAI, OpenAI, Anthropic, Ollama, and Microsoft Foundry. I pointed it at our Azure OpenAI instance:
from microsoft.agents.ai import AIAgent
from azure.identity import AzureCliCredential
agent = AIAgent(
endpoint="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project",
credential=AzureCliCredential(),
model="gpt-4o-mini"
)
response = agent.invoke("What Azure services are running in our subscription?")
print(response.content)
This worked immediately, which was encouraging. But a chatbot that can only talk isn't a DevOps agent—it's a fancy FAQ. The real power comes when you give it tools.
Adding DevOps Tools
Step 2 of the framework's tutorial covers function tools, and this is where things got interesting for my use case. I defined tools that let the agent actually interact with our Azure infrastructure:
from microsoft.agents.ai import tool
@tool
def get_alerts(severity: str = "Sev1") -> str:
"""Fetch active Azure Monitor alerts filtered by severity."""
# Real implementation uses Azure SDK
from azure.mgmt.monitor import MonitorManagementClient
client = MonitorManagementClient(credential, subscription_id)
alerts = client.alerts.list_by_subscription()
active = [a for a in alerts if a.properties.severity == severity]
return str([{"name": a.name, "state": a.properties.monitor_condition} for a in active])
@tool
def restart_app_service(resource_group: str, app_name: str) -> str:
"""Restart an Azure App Service."""
from azure.mgmt.web import WebSiteManagementClient
client = WebSiteManagementClient(credential, subscription_id)
client.web_apps.restart(resource_group, app_name)
return f"Restarted {app_name} in {resource_group}"
@tool
def check_runbook(runbook_name: str) -> str:
"""Retrieve steps from our incident runbook wiki."""
# Fetches from our internal documentation API
import requests
resp = requests.get(f"https://wiki.internal/runbooks/{runbook_name}")
return resp.text
The framework handles schema parsing and tool definition automatically. I didn't have to manually write JSON schemas—it inspects the function signatures and docstrings. That saved me a surprising amount of boilerplate.
Multi-Turn Conversations and State
A real incident isn't a single question. You investigate, take action, check the result, and iterate. MAF uses "sessions" for multi-turn state management:
from microsoft.agents.core import AgentSession
session = AgentSession(agent=agent)
# First turn: discover the problem
response1 = session.invoke("Check for Sev1 alerts in production")
# Agent calls get_alerts, finds "app-api-prod" is failing
# Second turn: investigate
response2 = session.invoke("What does the runbook say about app-api-prod failures?")
# Agent calls check_runbook, retrieves remediation steps
# Third turn: take action
response3 = session.invoke("The runbook says to restart. Go ahead and restart app-api-prod in rg-production")
# Agent calls restart_app_service
The session maintains conversation history automatically. This is a huge step up from stringing together independent API calls.
Memory and Persistence: Don't Repeat Yourself
One thing that drove me crazy in our old manual process was repeating the same context. "Which resource group is that in again?" "What's the connection string for the staging database?" MAF's context providers let you inject persistent information:
from microsoft.agents.core import ContextProvider
class InfrastructureContextProvider(ContextProvider):
async def get_context(self) -> str:
return """
Production Resource Group: rg-production
Staging Resource Group: rg-staging
Primary Region: eastus
Secondary Region: westus2
On-Call Rotation: Check https://pagerduty.internal/current
Escalation Policy: Sev1 requires manager approval before database changes
"""
agent.add_context_provider(InfrastructureContextProvider())
Now the agent always knows our resource group names and regions without being told every time. More importantly, I embedded our escalation policy right in the context—so the agent knows it can't make database changes without approval.
Workflows: The Real Power Move
Individual agents are useful, but the framework's graph-based workflows are where DevOps automation gets serious. I built a workflow that chains together alert detection, runbook lookup, and remediation with human-in-the-loop approval:
from microsoft.agents.workflows import Workflow, step
@step
async def detect_alerts(context):
alerts = get_alerts(severity="Sev1")
context["alerts"] = alerts
return "investigate"
@step
async def investigate(context):
for alert in context["alerts"]:
runbook = check_runbook(alert["name"])
context["runbook_steps"] = runbook
return "approve"
@step
async def approve_action(context):
# Human-in-the-loop: pause and wait for approval
print(f"Proposed action based on runbook: {context['runbook_steps']}")
approval = input("Approve? (yes/no): ")
if approval.lower() == "yes":
return "execute"
return "abort"
@step
async def execute_remediation(context):
# Execute the approved steps
restart_app_service("rg-production", "app-api-prod")
return "done"
workflow = Workflow()
workflow.add_step("detect", detect_alerts)
workflow.add_step("investigate", investigate)
workflow.add_step("approve", approve_action)
workflow.add_step("execute", execute_remediation)
workflow.add_transition("detect", "investigate")
workflow.add_transition("investigate", "approve")
workflow.add_transition("approve", "execute")
workflow.add_transition("approve", "abort")
The human-in-the-loop checkpointing was the feature I didn't know I needed. When the agent wants to restart a production service, it pauses and waits for my approval. I can inspect what it's planning to do and why, then approve or reject. This is critical for DevOps—you cannot have an agent autonomously restarting production services without a human signing off.
The Agent Harness for Complex Tasks
For longer, multi-step operations, MAF offers a "Harness" agent with built-in planning, todo tracking, and context compaction. I tested this with a complex deployment validation scenario:
from microsoft.agents.harness import HarnessAgent
harness = HarnessAgent(
agent=agent,
tools=[get_alerts, check_runbook],
enable_planning=True,
enable_todo_tracking=True
)
response = harness.invoke("""
Validate that the v2.3.1 deployment to production is healthy:
1. Check for any new Sev2+ alerts
2. Verify the health check endpoints are returning 200
3. Compare error rates to the pre-deployment baseline
4. If any issues found, look up the rollback runbook
""")
The harness agent created a plan, tracked its progress through each step, and compacted context when the conversation got long. This is genuinely useful for deployment validation, which is a multi-step process where you need to maintain focus across many checks.
Honest Limitations
After a few weeks of using MAF for DevOps tasks, here's my honest assessment:
The framework is still in preview. I hit a few rough edges—some documentation links 404'd, and the Go version is missing several features (no declarative agents, no RAG, no CodeAct yet). If you're a Go shop, you'll want to wait.
Tool security needs careful thought. The "don't-ask-again" tool approval feature is convenient but dangerous in DevOps. I accidentally approved a tool permanently that I should have only approved once. Now I always use the per-invocation approval mode for infrastructure-modifying tools.
Local development doesn't perfectly map to cloud deployment. The framework promises smooth deployment to Azure, but I found myself rewriting some local tool implementations when moving to Azure Functions hosting. The hosting story is getting better, but it's not seamless yet.
Cost can creep up on you. When the harness agent is doing multi-step planning with GPT-4o, those tokens add up fast. I switched to GPT-4o-mini for routine checks and only escalated to GPT-4o for complex incident triage. Monitor your Azure OpenAI usage carefully.
It's not a replacement for your existing IaC. MAF is for dynamic, reasoning-based automation—incident response, deployment validation, runbook execution. It doesn't replace Terraform, Bicep, or your CI/CD pipelines. Think of it as a complementary layer for the tasks that require judgment and adaptability.
Practical Tips
Start with read-only tools. Before giving an agent the ability to modify infrastructure, let it run in observation mode for a week. Have it fetch alerts, check statuses, and surface information. Build trust gradually.
Put guardrails in your context providers. Embed your escalation policies, change freezes, and restricted actions directly in the context. The LLM will respect these instructions most of the time (though not always, which is why human-in-the-loop matters).
Use the workflow checkpointing. For any multi-step DevOps process, build a workflow with explicit approval steps. The checkpointing also means you can resume interrupted workflows—critical when you're dealing with incidents that span hours.
Log everything. MAF has observability built in, but you'll want to pipe agent actions into your existing monitoring stack. I send all agent tool invocations to Application Insights alongside our other Azure telemetry.
Version your agent configurations. Treat your agent definitions, tool configurations, and context providers like code. Put them in git, review changes in PRs, and deploy them through your CI/CD pipeline. An agent is infrastructure.
Microsoft Agent Framework isn't perfect, but it's the first agent framework I've used that feels like it was designed for production DevOps workloads rather than just demos. The combination of structured workflows, human-in-the-loop approval, and persistent memory addresses the real problems I face in incident response. It's worth your time to experiment with—just keep a human in the loop until you've built enough trust to let the agent work more autonomously.