Blog Post

Microsoft Developer Community Blog
8 MIN READ

Deploying Foundry Hosted Agents from Source Code

j_folberth's avatar
j_folberth
Icon for Microsoft rankMicrosoft
Jun 10, 2026

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.

 

Updated Jun 10, 2026
Version 1.0
No CommentsBe the first to comment