This is the second post in our series on the Microsoft agent platform. Here we dive deep into building autonomous agents, the development experience, the Microsoft Agent Framework, tool design patterns, and how the GitHub Copilot SDK brings conversational AI to your agent system.
All examples reference the FibreOps repository, an autonomous fibre outage response system demonstrated at Microsoft Build BRK241.
The Microsoft Agent Framework
The Microsoft Agent Framework (now GA) provides a unified programming model for building agents. It supports multiple backends through a single .run() contract:
- Hosted —
FoundryAgentconnected to a Prompt Agent published to Microsoft Foundry Agent Service. - Foundry —
Agent + FoundryChatClientwith the definition resolved locally (ideal for prompt iteration). - Local — Deterministic
LocalAgentfor offline development and testing.
This design means your orchestration code never changes regardless of where the agent runs. The factory pattern in FibreOps selects the backend at startup:
# src/fibreops/agents/factory.py — simplified
from agent_framework_foundry import FoundryAgent
from agent_framework import Agent, FoundryChatClient
def build_agent(role: str, backend: str, config: Config):
if backend == "hosted":
return FoundryAgent(agent_id=config.foundry_agents[role])
elif backend == "foundry":
return Agent(
instructions=get_instructions(role),
chat_client=FoundryChatClient(endpoint=config.endpoint),
tools=get_tools(role),
)
else:
return LocalAgent(role=role)
Set FIBREOPS_AGENT_BACKEND to override the backend, or leave it as auto for intelligent detection.
Designing Role-Specialised Agents
FibreOps demonstrates a key pattern: role specialisation. Rather than one monolithic agent, the system uses three focused agents, each with a clear responsibility boundary:
| Agent | Role | Tools Available |
|---|---|---|
| IncidentAnalysisAgent | Classify severity, find root cause, retrieve SOP | Knowledge (SOPs + topology), Web IQ, Work IQ |
| NetOpsCoordinatorAgent | File D365 incident, post Teams notice | Ticketing, Teams, Memory |
| FieldDispatchAgent | Select engineer, book resource, update team | Dispatch, Teams, Voice |
Why Role Specialisation?
- Focused system prompts — Each agent has a tightly scoped instruction set, reducing hallucination and improving reliability.
- Independent evaluation — You can score each agent separately against role-specific criteria.
- Parallel development — Teams can iterate on agents independently.
- Selective upgrade — Swap one agent's model or implementation without touching others.
Tool Design: Typed Python Functions
Tools in the Microsoft Agent Framework are typed Python functions that the runtime supplies to the hosted agent definition. FibreOps demonstrates several tool categories:
Knowledge Tools
# src/fibreops/tools/knowledge.py — simplified
def sop_lookup(node_id: str, signal_type: str) -> dict:
"""Retrieve the Standard Operating Procedure for a given signal type.
Args:
node_id: The fibre node identifier (e.g., FN-LDN-001)
signal_type: The type of signal (loss_of_light, high_ber, signal_degradation)
Returns:
SOP with steps, escalation path, and estimated resolution time.
"""
# Load from local markdown SOPs or Foundry IQ
...
def web_iq_search(query: str, *, limit: int = 5) -> list[dict]:
"""Search public web for context relevant to the incident.
Grounding against roadworks, weather, power outages, splice guidance.
Falls back to deterministic fixtures when endpoint is unset.
"""
...
def work_iq_search(query: str, *, limit: int = 5) -> list[dict]:
"""Search enterprise knowledge for context relevant to the incident.
Site surveys, SLA tiers, competency matrix, MTTR trends.
"""
...
Integration Tools
# src/fibreops/tools/teams.py — simplified
def post_outage_notice(
incident_id: str,
node_id: str,
severity: str,
summary: str,
engineer: str | None = None,
) -> dict:
"""Post an Adaptive Card outage notice to the configured Teams channel.
If TEAMS_WEBHOOK_URL is not set, appends to state/teams_outbox.jsonl
for offline review.
"""
card = build_adaptive_card(incident_id, node_id, severity, summary, engineer)
if config.teams_webhook_url:
requests.post(config.teams_webhook_url, json=card)
else:
append_to_outbox(card)
return {"status": "posted", "incident_id": incident_id}
Design Principles for Agent Tools
- Typed parameters with docstrings — The runtime uses type hints and docstrings to generate the tool schema for the LLM.
- Graceful degradation — Every tool works offline by falling back to local fixtures or file-based state.
- Idempotent where possible — Tools that create resources return existing records if called with the same parameters.
- Observable — Every tool invocation emits an OpenTelemetry span for tracing and debugging.
The Orchestrator Pattern
The orchestrator drives signals through the agent pipeline. It is deliberately simple — a linear flow with error handling:
# src/fibreops/orchestrator.py — simplified
async def handle_signal(signal: TelemetrySignal) -> RunResult:
"""Process a telemetry signal through the agent pipeline."""
# Stage 1: Incident Analysis
analysis = await incident_agent.run(
f"Analyse this signal: {signal.model_dump_json()}"
)
# Stage 2: NetOps Coordination
coordination = await netops_agent.run(
f"Coordinate response for: {analysis.summary}"
)
# Stage 3: Field Dispatch
dispatch = await dispatch_agent.run(
f"Dispatch engineer for incident: {coordination.incident_id}"
)
return RunResult(
signal=signal,
analysis=analysis,
coordination=coordination,
dispatch=dispatch,
)
The orchestrator honours the same contract regardless of backend — hosted, foundry, or local — because all backends implement await agent.run(prompt).
GitHub Copilot SDK Integration (GA)
The GitHub Copilot SDK enables conversational interaction with your agent system. FibreOps implements FibreOpsCopilotClient with the same interface as github/copilot-sdk:
# src/fibreops/sdk/__init__.py — simplified
from fibreops.sdk.client import FibreOpsCopilotClient
client = FibreOpsCopilotClient()
session = client.create_session()
# Query agent status
response = session.send_and_wait("status")
print(response.text) # Human-readable summary
print(response.data) # Structured JSON
# Inject a telemetry signal via conversation
response = session.send_and_wait(json.dumps({
"signal_id": "sig-demo",
"node_id": "FN-LDN-001",
"signal_type": "loss_of_light",
"severity": "critical"
}))
The adapter routes prompts by shape:
- JSON signal-shaped dicts — Forwarded to the orchestrator for processing.
- Free-form text — Answered by a deterministic responder (
help,status,nodes,engineers,optimiser,dispatch).
Drive it from the terminal:
python -m fibreops.demo chat "help"
python -m fibreops.demo chat "status"
python -m fibreops.demo chat '{"signal_id":"sig-demo","node_id":"FN-LDN-001","signal_type":"loss_of_light","severity":"critical"}'
Or hit the embedded HTTP endpoint when the NOC console is running:
Invoke-RestMethod -Method Post http://127.0.0.1:8800/sdk/chat -Body '{"prompt":"status"}' -ContentType application/json
Development Workflow with Foundry Toolkit for VS Code
The Foundry Toolkit for VS Code provides an integrated development experience:
- Author prompts — Edit system instructions with live preview and token counting.
- Test locally — Run against the
foundrybackend withFoundryChatClientpointing at your development model. - Iterate fast — The
foundrybackend resolves definitions locally, so prompt changes take effect immediately without republishing. - Publish when ready —
python -m fibreops.demo publishcreates hosted Prompt Agents in Foundry.
Multi-Model Support
The Microsoft Agent Framework supports multiple models. FibreOps defaults to gpt-4.1-mini (the model available in most demo Foundry accounts), but any chat-completions deployment works:
# .env
AZURE_AI_MODEL_DEPLOYMENT=gpt-4.1-mini # or gpt-4o-mini, gpt-4o, gpt-4.1
The framework also supports Claude Code connectors and Magentic-One for multi-agent collaboration scenarios.
Testing Strategy
FibreOps demonstrates a layered testing approach:
- Unit tests — Test tools in isolation with mocked dependencies.
- Local backend tests — Run the full pipeline with
LocalAgentfor deterministic assertions. - Integration tests — Run against real Foundry agents with
pytest -q. - Rubric evaluation — The optimizer scores every run against defined criteria.
# Run the test suite
.\.venv\Scripts\python.exe -m pytest -q
Key Takeaways
- The Microsoft Agent Framework provides a unified
.run()contract across hosted, foundry, and local backends. - Role specialisation keeps agents focused, testable, and independently evolvable.
- Tools are typed Python functions with docstrings — the runtime generates schemas automatically.
- The GitHub Copilot SDK (GA) enables conversational interaction with any agent system.
- Graceful degradation means the entire system works offline for development.
- The factory pattern lets you switch backends without changing orchestration code.
Next Steps
- Clone the FibreOps repository and run
python -m fibreops.demo --signals 3 - Microsoft Agent Framework documentation
- Next in this series: Running Hosted Agents in Microsoft Foundry Agent Service