>_ The Manifest

Our HR Onboarding Buddy works great when you hit F5: but the moment a second developer joins the project, “deploy it from my machine” stops being a strategy. CI/CD pipelines for declarative agents follow the same principles as any application: automate everything, keep environments consistent, and never let a manual step stand between a merged PR and production.

Why CI/CD for Declarative Agents?

Declarative agents are configuration, not compiled code: but that doesn’t mean they’re immune to deployment problems. A bad instruction edit, a mismatched environment variable, or a forgotten manifest update can break the agent just as badly as a null pointer in a backend service.

CI/CD gives you:

  • Consistency: Every deployment follows the same steps, regardless of who triggers it
  • Speed: Push to main, agent updates in production. No manual zip uploads.
  • Auditability: Every change is traceable through git history and pipeline logs
  • Environment isolation: Test in dev, validate in staging, deploy to production

The Microsoft 365 Agents Toolkit CLI (atk) makes this straightforward. The same commands you run locally (atk provision, atk deploy, atk publish) work identically in a pipeline.

The Lifecycle File: m365agents.yml

Every Agents Toolkit project includes a lifecycle file that defines what happens during provisioning and deployment. This file is named m365agents.yml.

Here’s what a typical lifecycle file looks like for the HR Onboarding Buddy:

version: v1.10

provision:
  - uses: teamsApp/create
    with:
      name: HR Onboarding Buddy-${{TEAMSFX_ENV}}
    writeToEnvironmentFile:
      teamsAppId: TEAMS_APP_ID

  - uses: teamsApp/zipAppPackage
    with:
      manifestPath: ./appPackage/manifest.json
      outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
      outputFolder: ./appPackage/build

  - uses: teamsApp/update
    with:
      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip

publish:
  - uses: teamsApp/create
    with:
      name: HR Onboarding Buddy-${{TEAMSFX_ENV}}
    writeToEnvironmentFile:
      teamsAppId: TEAMS_APP_ID

  - uses: teamsApp/zipAppPackage
    with:
      manifestPath: ./appPackage/manifest.json
      outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
      outputFolder: ./appPackage/build

  - uses: teamsApp/publishAppPackage
    with:
      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
    writeToEnvironmentFile:
      publishedAppId: TEAMS_APP_PUBLISHED_APP_ID

Two stages:

  • Provision: Registers the Microsoft 365 app and packages the manifest
  • Publish: Creates the app, zips the package, and submits to your organization’s app catalog for admin approval
📝 Note

For pure declarative agents, there is no deploy stage. The provision and publish stages handle everything.

Setting Up GitHub Actions

The Agents Toolkit CLI is an npm package, so any CI runner with Node.js can execute it. Here’s a complete GitHub Actions workflow that provisions and publishes the HR Onboarding Buddy on every push to main:

name: Deploy Declarative Agent

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Agents Toolkit CLI
        run: npm install -g @microsoft/m365agentstoolkit-cli

      - name: Provision
        run: atk provision --env production
        env:
          M365_ACCOUNT_NAME: ${{ secrets.M365_ACCOUNT_NAME }}
          M365_ACCOUNT_PASSWORD: ${{ secrets.M365_ACCOUNT_PASSWORD }}
          M365_TENANT_ID: ${{ secrets.M365_TENANT_ID }}

      - name: Publish
        run: atk publish --env production
        env:
          M365_ACCOUNT_NAME: ${{ secrets.M365_ACCOUNT_NAME }}
          M365_ACCOUNT_PASSWORD: ${{ secrets.M365_ACCOUNT_PASSWORD }}

Each step maps to a lifecycle stage in m365agents.yml. The CLI reads the lifecycle file, resolves environment variables, and executes each action in order.

💡 Tip

Store credentials in GitHub repository secrets: never in your YAML. The M365_TENANT_ID, M365_ACCOUNT_NAME, and M365_ACCOUNT_PASSWORD values should be configured under Settings > Secrets and variables > Actions.

Validating Before Deploying

Add a validation step before provisioning to catch manifest issues early:

      - name: Validate manifest
        run: atk validate --app-package-file-path ./appPackage/manifest.json

This catches schema errors, missing fields, and invalid references before the pipeline spends time provisioning resources.

Azure DevOps Pipelines

The same CLI commands work in Azure DevOps. The YAML syntax differs, but the logic is identical:

trigger:
  branches:
    include:
      - main

pool:
  vmImage: "ubuntu-latest"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "20.x"

  - script: npm install -g @microsoft/m365agentstoolkit-cli
    displayName: "Install ATK CLI"

  - script: atk provision --env production
    displayName: "Provision"
    env:
      M365_ACCOUNT_NAME: $(M365_ACCOUNT_NAME)
      M365_ACCOUNT_PASSWORD: $(M365_ACCOUNT_PASSWORD)
      M365_TENANT_ID: $(M365_TENANT_ID)

  - script: atk publish --env production
    displayName: "Publish"
    env:
      M365_ACCOUNT_NAME: $(M365_ACCOUNT_NAME)
      M365_ACCOUNT_PASSWORD: $(M365_ACCOUNT_PASSWORD)
⚠️ Warning

In Azure DevOps, store secrets using pipeline variables marked as secret, or link them from an Azure Key Vault variable group. Never hardcode credentials in pipeline YAML.

Multi-Environment Strategy

Most teams need at least two environments: dev for iteration and production for the real deployment. The Agents Toolkit handles this through environment files in the env/ folder.

env/
├── .env.dev              # Dev tenant app IDs and settings
├── .env.dev.user         # Dev credentials (git-ignored)
├── .env.production       # Production tenant app IDs
└── .env.production.user  # Production credentials (git-ignored)

Each environment gets its own Entra ID app registration and Microsoft 365 app ID. When you run atk provision --env dev, the CLI reads from .env.dev. When the pipeline runs atk provision --env production, it reads from .env.production. Same lifecycle file, different targets.

This means your dev agent and production agent are completely independent: different app registrations, different tenant configurations. A bad instruction change in dev doesn’t touch production until the PR merges and the production pipeline runs.

Best Practices

Validate the manifest first. Run atk validate as the first pipeline step. A malformed manifest.json will fail the whole pipeline: catch it fast.

Use branch-based triggers. Deploy to dev on every push to feature branches, but only promote to production on merges to main. This is standard CI/CD hygiene, and it applies to declarative agents just as much as any other project.

Pin the CLI version. Instead of always installing the latest CLI, pin a version to avoid surprise breaking changes:

npm install -g @microsoft/[email protected]

Review instruction changes in PRs. Your agent’s instructions file has more impact on user experience than most code changes. Treat instruction diffs with the same scrutiny you give API contracts.

Keep secrets out of environment files. The .env.*.user files are git-ignored by default: never commit them. In your pipeline, inject credentials through repository secrets (GitHub) or variable groups (Azure DevOps), not through checked-in files.

The Value of Automated Agent Deployments

Setting up a CI/CD pipeline for your declarative agent is a one-time investment that pays dividends on every release:

  • No more “works on my machine”: Every deployment follows the same atk provision and atk publish sequence, executed identically by the pipeline runner on every merge. Human error in manual deployment steps is eliminated.
  • Full traceability: Every agent update is tied to a git commit, a pull request, and a pipeline run. You know exactly who changed what, when, and why.
  • Safe iteration: Isolated dev and production environments mean you can experiment freely on a feature branch, validate in dev, and only promote to production when a PR merges. A bad instruction change in dev never touches production until you explicitly let it through.
  • Fast releases: Push to main and the agent is provisioned and published automatically. No manual zip uploads, no Admin Center clicks, no waiting for someone with the right permissions to be online.
  • Scalable governance: As your agent estate grows, the same pipeline pattern applies to every new agent. Validation before provisioning, pinned CLI versions, and secrets in vault: these practices scale from one agent to fifty.

Treat your declarative agents like any other production software. The tooling is already there.

Resources


Have questions or want to share what you’re building? Connect with me on LinkedIn.

Have questions or want to share what you're building? Connect with me on LinkedIn or check out more on The Manifest.