Introduction
At Microsoft Build, it was announced that Foundry Hosted Agents now support source-code deployments. Previously, Hosted Agents required application code to be packaged in a container for deployment. This new functionality allows you to deploy the agent from a `.zip` file instead of from a container image.
This post walks through the process of deploying a source-code Hosted Agent, briefly compares that approach to container-based Hosted Agent deployment, and provides a reusable GitHub Action for CI/CD deployments. It is part of a series of post whose source code is housed in simple-hosted-agent-responses repository.
If Hosted Agents are new to you, read the previous posts, "Deploying Foundry Hosted Agents via REST API" and "GitHub Actions for Deploying Hosted Agents."
Background
A Foundry Hosted Agent helps abstract the management of the compute tier for your agent. It runs in a self-contained Micro-VM sandbox, meaning the Hosted Agent sandbox provides the CPU and memory allocation used to run your agent.
Previously, this Micro-VM would download your code from an Azure Container Registry (ACR) and run it on the virtualized platform.
Not all customers use container-based workloads today and, let's face it, not everything needs to be a container. So how do those customers and platforms take advantage of Foundry Hosted Agents? The answer is through source-code deployments of Foundry Hosted Agents.
What is a Source Code Agent?
Source Code Agents are like other Foundry Hosted Agents. The key deployment difference is that the code asset is a .zip file instead of a container image. This also changes the Agent Development Lifecycle compared with the containerized version of Foundry Hosted Agents.
An important point of clarity: the way the agent is configured is a data plane operation. As such, taking advantage of Source Code Agent functionality does not require changes to the Foundry infrastructure itself when your Infrastructure as Code (IaC) is only provisioning the supporting resources in Bicep, Terraform, or PowerShell. The deployment change happens through the Foundry data plane.
First, let's look at a container-based Foundry Hosted Agent:
![]()
Container-based Foundry Hosted Agent showing a Code asset that points to an Azure Container Registry image.
Now, let's compare it to the source-code version:
![]()
Source-code Foundry Hosted Agent showing a Code asset that points to a ZIP file.
Deployment Process
Now that we've looked at the end result, let's talk through the steps required to deploy a Foundry Hosted Agent via source code.
So in Foundry, what does the difference between a container-based and a source-code-based Foundry Hosted Agent look like? The Microsoft Learn docs outline this well:
Every source-code deployment follows the same sequence: package -> create or update -> poll until active -> invoke. The source-code path uses `code_configuration` in the agent definition; the image-based path uses `container_configuration` instead--the two are mutually exclusive on a single version.
If wanting to confirm and see in more detail one can refer to the Foundry Agent REST API documentation.
The source layout can stay familiar, but the deployed artifact changes to a `.zip` file. Packaging the source code into a ZIP is the piece that differs from the container-image flow. The agent deployment to Foundry is also slightly different because it uses source-code configuration instead of container configuration.
You can run this via `azd` with a command structured like the following:
azd ai agent init --no-prompt --project-id "<project-resource-id>" --deploy-mode code --runtime python_3_13 --entry-point main.py
This assumes `azd` is installed and authenticated, and that the authenticated identity has access to the Foundry project. The command initializes a code deployment for the project.
However, we recognize that the majority of enterprise organizations will want to use other deployment methods. As such, REST API deployments are supported, as are the Python and C# SDKs for creating the agent.
Taking this a step further, and similar to "GitHub Actions for Deploying Hosted Agents," let's create a reusable GitHub Action for deploying source-code-based Hosted Agents.
GitHub Action
If you are wanting to see the entire action it is part of the repository simple-hosted-agent-responses, which contains source code, IaC, and deployment options.
Background
First, we need to understand that we cannot reuse the GitHub Action from "GitHub Actions for Deploying Hosted Agents" because, as noted above, the REST API uses mutually exclusive options. In theory, we could add conditional logic across the parameters; however, it is cleaner to create a separate action.
Before invoking this action, the workflow must authenticate to Azure because the action calls `az account get-access-token` to acquire a token for the Foundry data plane.
Inputs
inputs:
project_endpoint:
description: Foundry project endpoint URL
required: true
agent_name:
description: Name of the hosted agent
required: true
source_code_zip:
description: Path to the local source-code zip artifact
required: true
model_deployment_name:
description: Name of the AI model deployment
required: true
cpu:
description: CPU allocation for the hosted agent container
required: false
default: '0.25'
memory:
description: Memory allocation for the hosted agent container
required: false
default: '0.5Gi'
runtime:
description: Source-code runtime for the hosted agent
required: false
default: 'python_3_13'
entry_point:
description: Source-code entry point command for the hosted agent
required: false
default: '["python", "main.py"]'
dependency_resolution:
description: How Agent Service resolves dependencies for the source-code deployment
required: false
default: 'remote_build'
max_polling_seconds:
description: Maximum time to wait for the source-code deployment to reach active status
required: false
default: '600'
For our inputs, `project_endpoint`, `agent_name`, `source_code_zip`, and `model_deployment_name` are required. The CPU, memory, runtime, entry point, dependency resolution, and max polling values are configurable properties with defaults set in the action.
The source-code-specific inputs populate the `code_configuration` properties of the REST payload. These include `source_code_zip`, `runtime`, `entry_point`, and `dependency_resolution`. This information tells Foundry how to run the code from the `.zip` package.
Outputs
We should output values that make sense for downstream workflows. Every workflow may not use them, but it is useful to expose non-secret values when they can support later steps.
In this case, we are creating a new version of the agent, so let's output that version ID.
outputs:
agent_version:
description: Version ID returned by the Foundry data plane
value: ${{ steps.post.outputs.agent_version }}
Action
The action maps the inputs to environment variables as the first step. After that, it gets an access token from Azure and calls the REST API endpoint.
Once we have this, we prepare the body of the call. Verify against the API for all valid properties. For this example, I chose not to set `rai_config` and `tools` to keep things simple.
runs:
using: composite
steps:
- name: Create source-code metadata
id: metadata
shell: bash
env:
AGENT_NAME: ${{ inputs.agent_name }}
MODEL_DEPLOYMENT_NAME: ${{ inputs.model_deployment_name }}
CPU: ${{ inputs.cpu }}
MEMORY: ${{ inputs.memory }}
RUNTIME: ${{ inputs.runtime }}
ENTRY_POINT: ${{ inputs.entry_point }}
DEPENDENCY_RESOLUTION: ${{ inputs.dependency_resolution }}
run: |
METADATA_FILE=$(mktemp)
ENTRY_POINT_JSON=$(python3 -c 'import json,sys; print(json.dumps(json.loads(sys.argv[1])))' "$ENTRY_POINT")
jq -n \
--arg model "$MODEL_DEPLOYMENT_NAME" \
--arg cpu "$CPU" \
--arg memory "$MEMORY" \
--arg runtime "$RUNTIME" \
--arg dep_resolution "$DEPENDENCY_RESOLUTION" \
--argjson entry_point "$ENTRY_POINT_JSON" \
'{
description: "Hosted agent deployed from source code",
definition: {
kind: "hosted",
protocol_versions: [{protocol: "responses", version: "1.0.0"}],
cpu: $cpu,
memory: $memory,
code_configuration: {
runtime: $runtime,
entry_point: $entry_point,
dependency_resolution: $dep_resolution
},
environment_variables: {AZURE_AI_MODEL_DEPLOYMENT_NAME: $model}
}
}' > "$METADATA_FILE"
echo "metadata_file=${METADATA_FILE}" >> "$GITHUB_OUTPUT"
echo "Metadata file created at ${METADATA_FILE}"
- name: Post source-code agent deployment to Foundry data plane
id: post
shell: bash
env:
PROJECT_ENDPOINT: ${{ inputs.project_endpoint }}
AGENT_NAME: ${{ inputs.agent_name }}
SOURCE_CODE_ZIP: ${{ inputs.source_code_zip }}
METADATA_FILE: ${{ steps.metadata.outputs.metadata_file }}
MAX_POLLING_SECONDS: ${{ inputs.max_polling_seconds }}
run: |
if [[ ! -f "$SOURCE_CODE_ZIP" ]]; then
echo "Error: Source code zip not found at ${SOURCE_CODE_ZIP}"
exit 1
fi
CODE_ZIP_SHA256=$(sha256sum "$SOURCE_CODE_ZIP" | awk '{print $1}')
echo "Source code SHA256: ${CODE_ZIP_SHA256}"
FOUNDRY_TOKEN=$(az account get-access-token \
--resource "https://ai.azure.com/" \
--query accessToken -o tsv)
# POST /agents/{name}/versions auto-creates the agent if it doesn't
# exist and adds a new version if it does, so a single call covers
# both first-deploy and update scenarios (matches update-agent).
HTTP_STATUS=$(curl -s -o /tmp/source_code_response.json \
-w "%{http_code}" \
-X POST \
"${PROJECT_ENDPOINT}/agents/${AGENT_NAME}/versions?api-version=2025-11-15-preview" \
-H "Authorization: Bearer ${FOUNDRY_TOKEN}" \
-H "Accept: application/json" \
-H "Foundry-Features: CodeAgents=V1Preview,HostedAgents=V1Preview" \
-H "x-ms-agent-name: ${AGENT_NAME}" \
-H "x-ms-code-zip-sha256: ${CODE_ZIP_SHA256}" \
-F "metadata=@${METADATA_FILE};type=application/json" \
-F "code=@${SOURCE_CODE_ZIP};type=application/zip;filename=${AGENT_NAME}.zip")
echo "HTTP ${HTTP_STATUS}: $(cat /tmp/source_code_response.json)"
if [[ "$HTTP_STATUS" -lt 200 || "$HTTP_STATUS" -ge 300 ]]; then
echo "Error: Foundry data plane returned HTTP ${HTTP_STATUS}"
exit 1
fi
RESPONSE=$(cat /tmp/source_code_response.json)
AGENT_VERSION=$(echo "$RESPONSE" | python3 -c 'import sys,json; print(json.load(sys.stdin)["version"])')
echo "agent_version=${AGENT_VERSION}" >> "$GITHUB_OUTPUT"
echo "Agent version resolved as ${AGENT_VERSION}"
START_TIME=$(date +%s)
while true; do
ELAPSED=$(($(date +%s) - START_TIME))
if [[ $ELAPSED -gt $MAX_POLLING_SECONDS ]]; then
echo "Error: Agent version did not reach active state within ${MAX_POLLING_SECONDS} seconds"
exit 1
fi
VERSION_STATUS=$(curl -s \
-X GET \
"${PROJECT_ENDPOINT}/agents/${AGENT_NAME}/versions/${AGENT_VERSION}?api-version=2025-11-15-preview" \
-H "Authorization: Bearer ${FOUNDRY_TOKEN}" \
-H "Accept: application/json" \
-H "Foundry-Features: CodeAgents=V1Preview,HostedAgents=V1Preview" \
| python3 -c 'import sys,json; data=json.load(sys.stdin); print(data.get("status", "unknown"))' 2>/dev/null)
echo "Current status: ${VERSION_STATUS} (elapsed ${ELAPSED}s)"
if [[ "$VERSION_STATUS" == "active" ]]; then
echo "Agent version ${AGENT_VERSION} is active"
break
fi
if [[ "$VERSION_STATUS" == "failed" ]]; then
echo "Error: Agent version reached failed status"
exit 1
fi
sleep 5
done
Building the Source-Code Artifact
Before calling the source-code Hosted Agent action, create the ZIP artifact that will be passed into `source_code_zip`.
source-code:
name: Build source-code artifact
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Create source-code zip artifact
run: |
git archive --format=zip --output=source-code.zip HEAD:src/agent-framework/responses/basic
- name: Upload source-code artifact
uses: actions/upload-artifact@v7
with:
name: source-code
path: source-code.zip
Calling the Action
Now that we have the action, how can we scale this across multiple workflows? We pass in the required parameters and the ZIP artifact path.
- name: Update agent with source code
uses: ./.github/actions/update-agent-source-code
with:
project_endpoint: ${{ needs.deploy-iac.outputs.project_endpoint }}
# Source-code agent shares the same Foundry project as the image-based
# agent; the `-src` suffix keeps them as distinct agent versions.
agent_name: ${{ inputs.agent_name }}-src
source_code_zip: ./.artifacts/source-code/source-code.zip
model_deployment_name: ${{ needs.deploy-iac.outputs.model_deployment_name }}
And just to show we can call the same action multiple times, here are two examples that do just that: Deploy (Bicep) and Deploy (Terraform).
Conclusion
Source-code deployments give Foundry Hosted Agents another deployment path for teams that do not want, or do not need, to package every agent as a container image. By using a .zip artifact, teams can keep a familiar source-code packaging flow while still taking advantage of the managed compute abstraction that Hosted Agents provide.
The reusable GitHub Action shown in this post turns that deployment process into a repeatable CI/CD step: package the source code, post the deployment to the Foundry data plane, poll until the new version is active, and expose the resulting agent version for downstream workflow steps. This keeps the deployment flexible while fitting into existing enterprise pipeline patterns.
For organizations already using container-based Hosted Agents, source-code deployments do not replace that model; they expand the options available. Choose the deployment approach that best fits how your teams package, govern, and operate their agent workloads.