First blog after 3 weeks of vacation, wish me luck! π
I need to be honest (and lost count already) but for the latest 10/15 blogs or so , weβve been calling Azure OpenAI directly. Build a prompt, fire an API call, parse the response. It works. Itβs simple. And for scripts you run from your own
machine, itβs exactly what you need.
But what if you want other people to use your AI tool? What if you want to deploy it somewhere, give it an endpoint, let it scale, let it handle multiple users, let it run without your laptop being on? Thatβs where it gets interesting, and thatβs where Hosted
Agents come in! And yes I’ve already created a blog on how to create agents etc. even hosted ones. But this time will be different!
Microsoft Foundry (formerly Azure AI Foundry) launched Hosted Agents as GA in July! The idea: you package your agent as a container, push it to Azure, and Foundry handles the rest! Compute, scaling, identity, sessions, monitoring. Your agent gets its
own endpoint that anyone (or anything) can call. How cool is that?! π
And the best part? You can manage the entire lifecycle from PowerShell. Build, deploy, invoke, monitor, delete, all from within the power of the terminal.
Like always, (this is my signature it seems)
throughout this post, youβll see π¬for action steps you can follow along with, and π for the technical deep-dives where I explain whatβs actually happening under the hood.
What Weβre Building
Three things. First: a minimal AI agent in Python that acts as a PowerShell expert. Yes, Python π€’ the Foundry agent protocol libraries only exist for Python and .NET. Donβt worry, itβs just like 30 or 50 lines and the Python is not the point.
Second: a PowerShell deployment script π that builds the container, pushes it to Azure Container Registry, deploys it to Foundry, and waits until itβs live. One command, fully automated.
Second: a PowerShell deployment script that builds the container, pushes it to Azure Container Registry, deploys it to Foundry, and waits until itβs live. One command, fully automated.
Prerequisites
This blog has more setup than usual. You need:
- A Microsoft Foundry project – if you donβt have one, create it in the Foundry
portal (ai.azure.com). - An Azure Container Registry – for storing your agentβs container image.
- Docker Desktop – for building the container.
- Azure CLI – for authentication and ACR access.
- A model deployment in your Foundry project (GPT-4o or similar).
π The Foundry project endpoint looks like https://youraccount.services.ai.azure.com/api/projects/your-project. Youβll find it in the Foundry portal under your project settings. This is different from the Azure OpenAI endpoint
weβve been using, itβs the Foundry data plane, where agents, conversations, and tools live.
π Authentication is also different. Instead of API keys, Hosted Agents use Entra ID tokens. Weβll grab these via az account the cloud handles the remaining part of the identity topic.
The Agent Code
The agent itself is Python. I know, I know π€’. But the Foundry protocol libraries only exist for Python and .NET (and I don’t want you to fully dive into .NET for this blog), so we donβt have a choice. The good news: itβs minimal.
π¬ Create a folder agent with three files
- agent/main.py and give it the content below
π I’ve been given comments that the variables I use in my blogs where not according how it should be done. Which is true, sometimes I place keys/secrets for my blogs in the code itself. As the learning goal during that time is not ‘how should i store a key/secret’ but showing you the theory on the topic we’re discussing. Well, during this post I’ll store it in an env variable. Again, if you need to store it somewhere else / more secure follow up on your security principles. This blog isn’t about telling you where to store them. π
import os
import asyncio
from azure.ai.agentserver.responses import (
CreateResponse,
ResponseContext,
ResponsesAgentServerHost,
TextResponse,
)
from openai import AzureOpenAI
api_key = os.environ.get("AZURE_OPENAI_API_KEY")
openai_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT", os.environ.get("FOUNDRY_PROJECT_ENDPOINT"))
model = os.environ.get("MODEL_DEPLOYMENT_NAME", "gpt-4o")
if api_key:
client = AzureOpenAI(
azure_endpoint=openai_endpoint,
api_key=api_key,
api_version="2024-10-21",
)
else:
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default")
client = AzureOpenAI(
azure_endpoint=openai_endpoint,
azure_ad_token_provider=token_provider,
api_version="2024-10-21",
)
app = ResponsesAgentServerHost()
SYSTEM_PROMPT = (
"You are an expert PowerShell assistant. "
"You help users write scripts, debug errors, and automate tasks. "
"Always provide working code examples. "
"Use PowerShell 7+ syntax where possible. "
"Be concise and practical."
)
@app.response_handler
async def handler(request: CreateResponse, context: ResponseContext, cancellation_signal: asyncio.Event):
input_text = await context.get_input_text()
completion = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": input_text},
],
)
answer = completion.choices[0].message.content
return TextResponse(context, request, text=answer)
if __name__ == "__main__":
app.run()
π The agent implements the Responses protocol, Foundryβs standard for conversational agents. When someone sends a message, create_async receives it, forwards it to GPT-4o via the Azure OpenAI SDK, and returns the answer as a TextResponse.
The FOUNDRY_PROJECT_ENDPOINT environment variable is injected automatically by the platform. The DefaultAzureCredential uses the agentβs managed identity.
- agent/requirements.txt and give it the content below (wont be using the latest versions here, check which are relevant for you)
azure-ai-agentserver-responses>=1.0.0b7
azure-identity>=1.21.0
openai>=1.60.0
uvicorn>=0.34.0- agent/Dockerfile and give it the content below
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8088
CMD ["python", "main.py"]Testing Locally
Never deploy something you havenβt tested. The nice thing about the Responses protocol is that your agent is just an HTTP server on port 8088. You can run it locally and poke it with PowerShell before touching Azure.
π¬Build and run the container locally (don’t forget to modify the project_endpoint)
docker build -t pshostedai:local .
docker run -p 8088:8088 -e MODEL_DEPLOYMENT_NAME=gpt-4o `
-e FOUNDRY_PROJECT_ENDPOINT=https://xxxxxxxxxxxxx.openai.azure.com/ `
-e AZURE_OPENAI_API_KEY=xxxxxxxxxxxxxx `
pshostedai:local
Docker build should not be spectaculair and should just show you the result of the build

π¬ Test with a simple HTTP request from PowerShell:
$body = @{ input = "How do I list all services in PowerShell?"; stream = $false } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "http://localhost:8088/responses" `
-ContentType "application/json" -Body $bodyYou should get a JSON response back with the agentβs answer. If that works, your agent is good to go.

And the details of the result:

Deploying with PowerShell
If you tested and know that your AI agent works locally it’s time to get it up and running in Azure!
π¬Create Deploy-FoundryAgent.ps1 and give it the content below
function Deploy-FoundryAgent {
param(
[Parameter(Mandatory)]
[string]$FoundryEndpoint,
[Parameter(Mandatory)]
[string]$AcrName,
[Parameter(Mandatory)]
[string]$AgentName,
[Parameter(Mandatory)]
[string]$OpenAIEndpoint,
[string]$ImageTag = "v1",
[string]$ModelDeployment = "gpt-4o",
[string]$Cpu = "1",
[string]$Memory = "2Gi",
[string]$AgentPath = ".\agent"
)
$acrHost = if ($AcrName -match '\.') { $AcrName } else { "$AcrName.azurecr.io" }
$imageName = "$acrHost/$($AgentName):$ImageTag"
Write-Host ""
Write-Host " Building container image..." -ForegroundColor Cyan
Write-Host " Image: $imageName" -ForegroundColor DarkGray
docker build --platform linux/amd64 -t $imageName $AgentPath
if ($LASTEXITCODE -ne 0) {
throw "Docker build failed."
}
Write-Host ""
Write-Host " Pushing to Azure Container Registry..." -ForegroundColor Cyan
az acr login --name $AcrName 2>$null
docker push $imageName
if ($LASTEXITCODE -ne 0) {
throw "Docker push failed. Check ACR permissions."
}
Write-Host ""
Write-Host " Deploying agent to Foundry..." -ForegroundColor Cyan
$token = az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$definition = @{
kind = "hosted"
image = $imageName
cpu = $Cpu
memory = $Memory
container_protocol_versions = @(
@{ protocol = "responses"; version = "1.0.0" }
)
environment_variables = @{
MODEL_DEPLOYMENT_NAME = $ModelDeployment
AZURE_OPENAI_ENDPOINT = $OpenAIEndpoint
}
}
$existingAgent = $null
try {
$checkUrl = "$FoundryEndpoint/agents/$AgentName`?api-version=v1"
$existingAgent = Invoke-RestMethod -Method Get -Uri $checkUrl -Headers $headers -ErrorAction SilentlyContinue
} catch {}
if ($existingAgent) {
Write-Host " Agent exists, deploying new version..." -ForegroundColor DarkGray
$url = "$FoundryEndpoint/agents/$AgentName/versions?api-version=v1"
$body = @{ definition = $definition }
} else {
Write-Host " Creating new agent..." -ForegroundColor DarkGray
$url = "$FoundryEndpoint/agents?api-version=v1"
$body = @{ name = $AgentName; definition = $definition }
}
$json = $body | ConvertTo-Json -Depth 20
$jsonBytes = [System.Text.Encoding]::UTF8.GetBytes($json)
try {
$response = Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $jsonBytes
$version = if ($response.version) { $response.version } else { $response.versions.latest.version }
Write-Host " Agent deployed: $AgentName (version $version)" -ForegroundColor Green
}
catch {
$errBody = $_.ErrorDetails.Message
Write-Host " Deploy failed: $errBody" -ForegroundColor Red
throw
}
Write-Host ""
Write-Host " Waiting for agent to become active..." -ForegroundColor Cyan
$statusUrl = "$FoundryEndpoint/agents/$AgentName`?api-version=v1"
$maxAttempts = 60
$attempt = 0
while ($attempt -lt $maxAttempts) {
Start-Sleep -Seconds 5
$attempt++
$token = az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv
$headers["Authorization"] = "Bearer $token"
$agentInfo = Invoke-RestMethod -Method Get -Uri $statusUrl -Headers $headers
$status = $agentInfo.versions.latest.status
Write-Host " [$attempt] Status: $status" -ForegroundColor DarkGray
if ($status -eq "active") {
Write-Host ""
Write-Host " Agent is live!" -ForegroundColor Green
Write-Host " Endpoint: $FoundryEndpoint/agents/$AgentName/endpoint/protocols/openai/responses" -ForegroundColor White
Write-Host ""
return [PSCustomObject]@{
AgentName = $AgentName
Version = $agentInfo.versions.latest.version
Status = "active"
Endpoint = "$FoundryEndpoint/agents/$AgentName/endpoint/protocols/openai/responses"
Image = $imageName
}
}
if ($status -eq "failed") {
Write-Host " Provisioning failed." -ForegroundColor Red
throw "Agent provisioning failed."
}
}
throw "Timeout waiting for agent to become active."
}
This script does everything: builds the Docker image, pushes it to ACR, creates the agent in Foundry via the REST API, and polls until itβs active.
β οΈ Make sure you have your project URL for foundry before continuing with the next step. In foundry you can find it here:

- Now run the command below to deploy everything (make sure the deployment matches yours)
. .\Deploy-FoundryAgent.ps1
$endpoint = "https://xxxxxxxxxxx-resource.services.ai.azure.com/api/projects/xxxxxxxxxxx"
Deploy-FoundryAgent `
-FoundryEndpoint "https://xxxxxxxxx-resource.services.ai.azure.com/api/projects/xxxxxxxxx" `
-AcrName "yourAcrName" `
-AgentName "ps-expert" `
-OpenAIEndpoint "https://xxxxxxxxx-resource.openai.azure.com" `
-ImageTag "v1"When everything is done you should see our agent running in foundry

π¬ After the first deploy, grab the agentβs principal_id from the response and assign AcrPull:
Grant AcrPull to the agentβs managed identity. This is the step that will bite you if you skip it. When Foundry deploys your agent, it needs to pull the container image from your ACR. But the agent runs under its own managed identity, and that identity has no access to your registry by default. Youβll get a cryptic ImageError: Container registry authentication failed at runtime.
You should use the foundry project principal id.
$principalId = "your-agent-principal-id"
$acrId = az acr show --name your-acr-name --query id -o tsv
az role assignment create `
--assignee $principalId `
--role "AcrPull" `
--scope $acrIdYou should see the operation succeed:

Now deploy the agent again (sorry I’m still trying out how to fix this in one run, my head still needs to keep up after vacation π)
Deploy-FoundryAgent `
-FoundryEndpoint "https://xxxxxxxxx-resource.services.ai.azure.com/api/projects/xxxxxxxxx" `
-AcrName "yourAcrName" `
-AgentName "ps-expert" `
-OpenAIEndpoint "https://xxxxxxxxx`
-ImageTag "v12"You also need to make sure your agent can communicate with the AI model so it needs permissions. You can do so by running the command below:
az role assignment create `
--assignee "[id here]" `
--role "Cognitive Services OpenAI User" `
--scope "/subscriptions/xxxxxxxxxxxxxxxxxxxxx/resourceGroups/xxxxxxxxxxxxxx/providers/Microsoft.CognitiveServices/accounts/[name of foundry]"
Chatting with Your Agent
Once the agent is active, you have two options. Send a single message, or start an interactive chat. You can directly interact with the agent through the foundry portal:

Or follow the steps below
π¬ Use the invoke script below for interaction with the agent (call it Invoke-FoundryAgent.ps1)
function Invoke-FoundryAgent {
param(
[Parameter(Mandatory)]
[string]$FoundryEndpoint,
[Parameter(Mandatory)]
[string]$AgentName,
[Parameter(Mandatory)]
[string]$Message
)
$token = az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$body = @{
input = $Message
stream = $false
store = $true
}
$url = "$FoundryEndpoint/agents/$AgentName/endpoint/protocols/openai/responses?api-version=v1"
$json = $body | ConvertTo-Json -Depth 10
$jsonBytes = [System.Text.Encoding]::UTF8.GetBytes($json)
Write-Host ""
Write-Host " Thinking..." -ForegroundColor Cyan
try {
$response = Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $jsonBytes
}
catch {
$errBody = $_.ErrorDetails.Message
Write-Host " Error: $errBody" -ForegroundColor Red
throw
}
$outputText = ($response.output | Where-Object { $_.type -eq "message" } |
ForEach-Object { $_.content } |
Where-Object { $_.type -eq "output_text" } |
ForEach-Object { $_.text }) -join "`n"
if ([string]::IsNullOrWhiteSpace($outputText)) {
$outputText = $response.output_text
}
Write-Host ""
Write-Host $outputText
Write-Host ""
return [PSCustomObject]@{
ResponseId = $response.id
Text = $outputText
Model = $response.model
Usage = $response.usage
}
}
function Start-FoundryChat {
param(
[Parameter(Mandatory)]
[string]$FoundryEndpoint,
[Parameter(Mandatory)]
[string]$AgentName
)
Write-Host ""
Write-Host " ==============================================" -ForegroundColor Cyan
Write-Host " Foundry Agent Chat: $AgentName" -ForegroundColor Cyan
Write-Host " ==============================================" -ForegroundColor Cyan
Write-Host " Type your question. Type 'exit' to quit." -ForegroundColor DarkGray
Write-Host ""
$history = @()
while ($true) {
Write-Host " You>" -NoNewline -ForegroundColor Green
$input = Read-Host " "
if ([string]::IsNullOrWhiteSpace($input)) { continue }
if ($input.Trim().ToLower() -eq "exit") {
Write-Host ""
Write-Host " Chat ended. $($history.Count) messages exchanged." -ForegroundColor Cyan
Write-Host ""
return
}
$result = Invoke-FoundryAgent -FoundryEndpoint $FoundryEndpoint -AgentName $AgentName -Message $input
$history += @{
question = $input
answer = $result.Text
}
}
}
function Get-FoundryAgentStatus {
param(
[Parameter(Mandatory)]
[string]$FoundryEndpoint,
[string]$AgentName
)
$token = az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
if ($AgentName) {
$url = "$FoundryEndpoint/agents/$AgentName`?api-version=v1"
$agent = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
$latestVersion = $agent.versions.latest
$status = $latestVersion.status
$version = $latestVersion.version
Write-Host ""
Write-Host " Agent: $($agent.name)" -ForegroundColor White
Write-Host " State: $($agent.state)" -ForegroundColor $(if ($agent.state -eq "enabled") { "Green" } else { "Yellow" })
Write-Host " Status: $status" -ForegroundColor $(if ($status -eq "active") { "Green" } else { "Yellow" })
Write-Host " Version: $version" -ForegroundColor DarkGray
Write-Host ""
return $agent
}
$url = "$FoundryEndpoint/agents?api-version=v1"
$agents = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
Write-Host ""
Write-Host " Hosted Agents:" -ForegroundColor White
Write-Host " βββββββββββββββββββββββββββββββββββββββ" -ForegroundColor DarkGray
$agentList = if ($agents.data) { $agents.data } else { $agents.value }
foreach ($a in $agentList) {
$aStatus = $a.versions.latest.status
if (-not $aStatus) { $aStatus = $a.state }
$statusColor = switch ($aStatus) {
"active" { "Green" }
"enabled" { "Green" }
"creating" { "Yellow" }
"failed" { "Red" }
default { "DarkGray" }
}
Write-Host " $($a.name)" -NoNewline -ForegroundColor White
Write-Host " [$aStatus]" -ForegroundColor $statusColor
}
Write-Host ""
return $agents.value
}
function Remove-FoundryAgent {
param(
[Parameter(Mandatory)]
[string]$FoundryEndpoint,
[Parameter(Mandatory)]
[string]$AgentName,
[switch]$Force
)
if (-not $Force) {
$confirm = Read-Host " Delete agent '$AgentName' and all versions? (y/N)"
if ($confirm -ne 'y') {
Write-Host " Cancelled." -ForegroundColor DarkGray
return
}
}
$token = az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv
$headers = @{
"Authorization" = "Bearer $token"
}
$url = "$FoundryEndpoint/agents/$AgentName`?api-version=v1"
try {
Invoke-RestMethod -Method Delete -Uri $url -Headers $headers
Write-Host " Agent '$AgentName' deleted." -ForegroundColor Green
}
catch {
$errBody = $_.ErrorDetails.Message
Write-Host " Delete failed: $errBody" -ForegroundColor Red
throw
}
}- Now let’s interact!
. .\Invoke-FoundryAgent.ps1
Invoke-FoundryAgent `
-FoundryEndpoint $endpoint `
-AgentName "ps-expert" `
-Message "How do I monitor a folder for new files?"
Cool right?! You now have containerized hosted agents directly available in Azure Foundry. Enjoy! π
The Bigger Picture
This blog is different from the previous sixty. Weβre not just calling an API, weβre deploying infrastructure. The agent runs in the cloud with its own identity, its own endpoint, its own scaling. Itβs a real service.
And the management layer is pure PowerShell. No portal clicking, no Azure DevOps pipelines (unless you want them). Just scripts that automate the entire agent lifecycle.
Some ideas for what you could deploy:
- A documentation bot that knows your internal runbooks
- An incident helper that reads logs and suggests fixes
- A code review agent that other tools can call via HTTP
- A chat assistant that your team accesses through a simple endpoint
The agent code can be as simple or complex as you want. Swap the system prompt, add more tools, integrate with MCP, the Foundry platform handles the hosting.
Wrapping Up
I must admit, this one was difficult for me. Starting fresh after my vacation with this idea in mind and the GA of the hosted agents in foundry it took me some evening hours to create it. But here it is!
I will save you the long wrapping up, at this point you’ve read enough, and to be honest, I’m already up to a next vacation π
Hope you all enjoyed it, and on to the next one!
And some cool URLs I’ve been using as well
https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents
https://devblogs.microsoft.com/foundry/agent-service-build2026/

