1190. This week, we look at what makes Yoda's English special, and we look at the difference between โtrooperโ and โtrouper,โ including whether singular โtroopโ may be short for โtrooperโ and why โa real trouperโ is the traditional spelling.
๐ Join the Grammar Girl Patreon.
๐ Share your familect recording in Speakpipe or by leaving a voicemail at 833-214-GIRL (833-214-4475)
๐ Watch my LinkedIn Learning writing courses.
๐ Subscribe to the newsletter.
๐ Find an edited transcript.
๐ Get Grammar Girl books.
| HOST: Mignon Fogarty
| Grammar Girl is part of the Quick and Dirty Tips podcast network.
| Theme music by Catherine Rannus.
| Grammar Girl Social Media: YouTube. TikTok. Facebook. Threads. Instagram. LinkedIn. Mastodon. Bluesky.
Hosted on Acast. See acast.com/privacy for more information.
Welcome in! Youโve entered, Only Malware in the Building. Join us each month to sip tea and solve mysteries about todayโs most interesting threats. Your host isย โ โ โ โ โ โ โ โ โ โ Selena Larsonโ โ โ โ โ โ โ โ โ โ ,ย โ โ โ โ โ โ โ โ โ โ Proofpointโ โ โ โ โ โ โ โ โ โ ย intelligence analyst and host of their podcastย โ โ โ โ โ โ โ โ โ โ DISCARDEDโ โ โ โ โ โ โ โ โ โ . Inspired by the residents of a building in New Yorkโs exclusive upper west side, Selena is joined by her co-hostsย โ โ โ โ โ โ โ โ โ โ N2Kย Networksโ โ โ โ โ โ โ โ โ โ ย โ โ โ โ โ โ โ โ โ โ Dave Bittnerโ โ โ โ โ โ โ โ โ โ and โ โ โ โ โ โ โ โ โ Keith Mularskiโ โ โ โ โ โ โ โ โ , former FBI cybercrime investigator and now Chief Global Ambassador at โ โ โ โ โ โ โ โ โ Qintelโ โ โ โ โ โ โ โ โ .
Being a security researcher is a bit like being a detective: you gather clues, analyze the evidence, and consult the experts to solve the cyber puzzle. This week, our hosts dive into the evolving threat of software supply chain attacks and the growing risks facing the open-source ecosystem. As developers increasingly rely on third-party packages and AI-powered coding tools, attackers are finding new ways to abuse trusted software to reach a wider range of targets. The discussion explores why these attacks are becoming more common, what recent incidents reveal about the state of software security, and what organizations can do to better protect themselves.
Sources:ย โ
Shai-Hulud worm returns stronger and more automated than ever beforeโ
โMini Shai-Huludโ malware compromises hundreds of open-source packages in sprawling supply-chain attackโ
What We Learned: Axios NPM Supply Chain Compromise Emergency Briefing
Your AI Gateway Was a Backdoor: Inside the LiteLLM Supply Chain Compromise
Most AI engineering teams can build a working agent in a day. The hard part is not building it; the hard part is operating it. Prompts drift. Tool configurations change without review. Deployments happen from someone's laptop. There is no audit trail, no rollback plan, and no consistent way to promote a change from a development environment to production.
GitOps closes that gap. By treating your agent definition, configuration, and infrastructure as version-controlled source code, you get the same delivery discipline that software engineering teams have applied to application code for years. Every change is reviewed, every deployment is automated, and every environment state is traceable to a specific commit.
This post shows you how to apply GitOps principles to a Microsoft Foundry Hosted Agent using GitHub as the source of truth and GitHub Tasks and Actions as the automation layer. The result is a repeatable, governed, production-ready delivery model for AI agents.
Microsoft Foundry is Microsoft's platform for building, deploying, and operating AI applications and agents. A Hosted Agent is an agent runtime managed by the Foundry platform rather than self-hosted by your team. You supply the agent logic, configuration, and tools; Foundry handles the runtime lifecycle, scaling, and managed infrastructure.
In practical terms, a Foundry Hosted Agent is a containerised agent application. You package your agent code, prompt definitions, tool bindings, and environment configuration into a container image. Foundry deploys and manages that container within a Foundry project, connected to models, tools, and observability infrastructure that the platform provides.
Teams choose Hosted Agents over self-hosting because:
Hosted Agents are a good fit when your team wants strong operational support with minimal platform overhead, when you need clear separation between environments, and when your agents depend on Azure AI capabilities such as Azure OpenAI Service, Azure AI Search, or Model Context Protocol integrations.
GitOps is straightforward for stateless web services: the code changes, the pipeline runs, the container is deployed. AI agents are more complex because there are multiple distinct artefacts that all affect agent behaviour:
Any one of these can change the behaviour of your agent in ways that are difficult to detect without structured review. A prompt change that looks harmless can alter tone, scope, or factual grounding. A tool configuration change can expose data to unintended callers. A model upgrade can shift response quality unpredictably.
Git gives you a single place to version, review, and approve all of these artefacts together. Pull requests give you a structured review gate. Workflow automation gives you validation before anything reaches a deployed environment. Tags and releases give you deployment markers you can roll back to.
The discipline of GitOps turns what is often an ad-hoc AI delivery process into a repeatable engineering practice.
The following diagram shows a practical reference architecture for delivering a Microsoft Foundry Hosted Agent through a GitOps model using GitHub.
+---------------------------+
| GitHub Repository |
| /src /agents /tools |
| /prompts /infra |
| /.github/workflows |
+---------------------------+
|
| Pull Request / Push to main
v
+---------------------------+
| GitHub Actions |
| 1. Validate agent config |
| 2. Lint and scan code |
| 3. Run unit tests |
| 4. Build container image |
| 5. Push to registry |
+---------------------------+
|
| Image tag (SHA or semver)
v
+---------------------------+
| Azure Container Registry |
| myregistry.azurecr.io |
| my-agent:<sha> |
+---------------------------+
|
+------+------+
| |
v v
+----------+ +----------+
| Foundry | | Foundry |
| Dev | | Test |
| Project | | Project |
+----------+ +----------+
|
Approval gate (GitHub env)
|
v
+----------+
| Foundry |
| Prod |
| Project |
+----------+
|
v
+---------------------------+
| Observability |
| Azure Monitor / App |
| Insights / Foundry Logs |
+---------------------------+
Key design decisions in this architecture:
Figure: GitOps delivery pipeline stages from commit to production
A well-structured repository separates agent logic from infrastructure and tooling from prompts. The following structure works well in practice:
my-foundry-agent/
โโโ .github/
โ โโโ workflows/
โ โ โโโ validate.yml # Runs on every PR
โ โ โโโ build-deploy.yml # Runs on merge to main
โ โ โโโ rollback.yml # Manual trigger workflow
โ โโโ CODEOWNERS # Review assignments by path
โโโ src/
โ โโโ agents/
โ โ โโโ agent.py # Agent entry point and orchestration
โ โ โโโ agent_config.json # Agent metadata and settings
โ โโโ tools/
โ โ โโโ search_tool.py # Tool implementations
โ โ โโโ data_tool.py
โ โโโ prompts/
โ โโโ system.txt # System prompt (versioned as plain text)
โ โโโ instructions.txt # Supplementary instructions
โโโ tests/
โ โโโ unit/ # Unit tests for tools and logic
โ โโโ integration/ # Integration tests against a running agent
โ โโโ smoke/ # Post-deployment smoke tests
โโโ infra/
โ โโโ main.bicep # Foundry project and resource definitions
โ โโโ environments/
โ โโโ dev.parameters.json
โ โโโ test.parameters.json
โ โโโ prod.parameters.json
โโโ scripts/
โ โโโ validate_agent.py # Config validation script
โ โโโ smoke_test.py # Smoke test runner
โโโ Dockerfile # Container image definition
โโโ docs/
โโโ architecture.md # Architecture and runbook documentation
What belongs where and why:
GitHub Tasks and Issues provide the work tracking layer on top of the GitOps delivery model. Used well, they connect the intention behind a change to its implementation and deployment history.
Practical patterns for using GitHub Tasks with agent delivery:
system.txt closes that issue, creating a permanent link between the rationale and the diff.The key insight is that GitHub Tasks are not just work management; they are part of your audit trail. A regulatory or security reviewer can follow the chain from a production deployment back through workflow runs, pull request reviews, and the original task that described the intent of the change.
The following walk-through describes a realistic developer experience for changing an agent prompt and promoting it to production.
src/prompts/system.txt, and updates any related unit tests.This flow means that no human ever deploys directly to any environment. Every environment state is traceable to a specific commit, image tag, and workflow run.
AI agents often have access to sensitive data and external systems. Security and governance cannot be an afterthought.
/infra/environments/ parameter files for this./src/prompts, /src/agents, and /infra via CODEOWNERS.A deployed agent that you cannot observe is an agent you cannot operate. Build observability in from the start.
One of the most important additions to the Foundry platform is Toolboxes, currently in Public Preview. If you have ever seen an agent codebase where three different agents each wire the same search tool with their own credentials and slightly different configurations, you already understand the problem Toolboxes solve.
A Toolbox is a named, versioned bundle of tools managed centrally in Microsoft Foundry. You define the tools once, configure authentication and access centrally, and publish a single MCP-compatible endpoint. Any agent in any runtime consumes that endpoint without per-tool wiring, custom SDK integration, or duplicated credential management.
Figure: Before and after Foundry Toolboxes. Each agent previously managed its own tool connections. With Toolboxes, agents connect to one governed endpoint.
Toolboxes are particularly well-suited to a GitOps delivery model because the toolbox definition is a discrete, versioned artefact. Instead of credentials and tool configuration scattered across agent codebases, the toolbox becomes its own managed entity with its own version history.
The key design property is that the toolbox endpoint URL is stable. When you promote a new toolbox version to be the default, agents consuming the endpoint pick up the update without any code changes. This means you can update tool configuration, add a new MCP server, or rotate credentials in the toolbox without redeploying every agent that uses it.
Figure: Toolbox versioning in a GitOps model. Commits trigger CI validation and deployment of new toolbox versions. The stable endpoint URL allows agents to consume updates without redeployment.
In your GitOps repository, toolbox definitions belong in /src/tools/toolbox_config.py or as a declarative configuration file checked into version control. The following example creates a toolbox that combines web search, Azure AI Search over internal documentation, and a GitHub MCP server:
# src/tools/toolbox_config.py
# Run this via CI to create or update a toolbox version in Foundry.
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
import os
client = AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=DefaultAzureCredential()
)
toolbox_version = client.beta.toolboxes.create_toolbox_version(
toolbox_name="customer-feedback-toolbox",
description="Tools for triaging customer feedback: search, docs, and GitHub.",
tools=[
{
"type": "web_search",
"description": "Search approved public documentation sites.",
"custom_search_configuration": {
"project_connection_id": os.environ["BING_CONNECTION_NAME"],
"instance_name": os.environ["BING_INSTANCE_NAME"]
}
},
{
"type": "azure_ai_search",
"name": "product-manuals-search",
"description": "Search internal product documentation.",
"azure_ai_search": {
"indexes": [
{
"index_name": os.environ["SEARCH_INDEX_NAME"],
"project_connection_id": os.environ["SEARCH_CONNECTION_ID"]
}
]
}
},
{
"type": "mcp",
"server_label": "github",
"server_url": "https://api.githubcopilot.com/mcp",
"project_connection_id": os.environ["GITHUB_CONNECTION_ID"]
}
],
)
print(f"Toolbox version created: {toolbox_version.version}")
print(f"MCP endpoint: {toolbox_version.mcp_endpoint}")
To promote a toolbox version to be the default (the endpoint agents use without specifying a version), add this to your deployment workflow:
# Promote toolbox version to default after validation
toolbox = client.beta.toolboxes.update(
toolbox_name="customer-feedback-toolbox",
default_version=toolbox_version.version,
)
print(f"Default version is now: {toolbox.default_version}")
The stable endpoint for agents consuming this toolbox is:
https://<your-project>.services.ai.azure.com/api/projects/<project>/toolbox/customer-feedback-toolbox/mcp?api-version=v1
In your agent code, connect to the toolbox via a single MCP tool definition. The agent gains access to every tool in the toolbox without knowing their individual configurations:
# src/agents/agent.py (relevant excerpt)
from agent_framework import MCPStreamableHTTPTool
import httpx, os
toolbox_endpoint = os.environ["FOUNDRY_TOOLBOX_ENDPOINT"]
http_client = httpx.AsyncClient(
auth=_ToolboxAuth(token_provider), # Microsoft Entra bearer token
timeout=120.0,
)
mcp_tool = MCPStreamableHTTPTool(
name="toolbox",
url=toolbox_endpoint,
http_client=http_client,
load_prompts=False,
)
# Agent now has access to web search, AI Search, and GitHub MCP
# through one tool definition and one authenticated connection.
Add a dedicated job to your build-deploy workflow to create and promote toolbox versions as part of the same CI/CD pipeline:
deploy-toolbox:
name: Deploy Toolbox Version
needs: validate
runs-on: ubuntu-latest
environment: dev
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Create toolbox version in Foundry
env:
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT_DEV }}
BING_CONNECTION_NAME: ${{ vars.BING_CONNECTION_NAME }}
BING_INSTANCE_NAME: ${{ vars.BING_INSTANCE_NAME }}
SEARCH_INDEX_NAME: ${{ vars.SEARCH_INDEX_NAME }}
SEARCH_CONNECTION_ID: ${{ vars.SEARCH_CONNECTION_ID }}
GITHUB_CONNECTION_ID: ${{ vars.GITHUB_CONNECTION_ID }}
run: python src/tools/toolbox_config.py
Key points to note:
Teams adopting this pattern commonly make the following mistakes. Identifying them early saves significant operational pain later.
agent_config.json mean your dev and prod configurations diverge at the source level. Use parameter files and environment variables resolved at deployment time.The following workflow runs on pull request validation and on merge to main. It covers the core delivery lifecycle: validate, build, deploy to dev, and smoke test.
# .github/workflows/build-deploy.yml
name: Build and Deploy Foundry Hosted Agent
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
REGISTRY: myregistry.azurecr.io
IMAGE_NAME: my-foundry-agent
jobs:
validate:
name: Validate Agent Configuration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Validate agent config schema
run: python scripts/validate_agent.py
- name: Run unit tests
run: pytest tests/unit/ -v
- name: Lint code
run: ruff check src/
build:
name: Build and Push Container Image
needs: validate
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
id-token: write
contents: read
outputs:
image_tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Log in to Azure Container Registry
run: az acr login --name ${{ env.REGISTRY }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,format=short
- name: Build and push image
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
deploy-dev:
name: Deploy to Foundry Dev
needs: build
runs-on: ubuntu-latest
environment: dev
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_DEV }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy agent to Foundry Dev project
run: |
az ai foundry agent deploy \
--project ${{ vars.FOUNDRY_PROJECT_DEV }} \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }} \
--environment dev
- name: Run smoke tests against dev
run: pytest tests/smoke/ -v --base-url ${{ vars.AGENT_URL_DEV }}
deploy-test:
name: Deploy to Foundry Test
needs: deploy-dev
runs-on: ubuntu-latest
environment: test
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_TEST }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy agent to Foundry Test project
run: |
az ai foundry agent deploy \
--project ${{ vars.FOUNDRY_PROJECT_TEST }} \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }} \
--environment test
- name: Run smoke tests against test
run: pytest tests/smoke/ -v --base-url ${{ vars.AGENT_URL_TEST }}
Key decisions in this workflow:
azure/login@v3 with id-token: write permissions. No long-lived secrets are stored in GitHub for Azure authentication.environment: test directive in the deploy-test job triggers a GitHub environment approval gate. A named reviewer must approve before the job runs.Use this checklist when adopting the GitOps pattern for a Microsoft Foundry Hosted Agent:
update default_version API step in the deployment workflow, not manually through the portalA Microsoft Foundry Hosted Agent is not something you deploy once and forget. Prompts evolve, tools change, models are upgraded, and policy requirements shift. Every one of those changes has the potential to alter agent behaviour in ways that affect users, costs, and compliance posture.
GitOps, implemented through GitHub and GitHub Tasks, gives you the operational discipline to manage that complexity. Source control for all artefacts. Pull request review for every change. Automated validation, build, and deployment. Environment promotion gates. A complete audit trail from task to production. These are not bureaucratic overhead; they are the foundation of reliable, trustworthy AI agent operations.
The teams that operate AI agents well are the ones that treat them like production software from the start. The investment in pipeline, structure, and governance pays back every time a change goes smoothly, every time a rollback takes minutes rather than hours, and every time a security or compliance reviewer can answer their question from a pull request history rather than a support ticket.
Build the discipline in early. Your future self, and your production environment, will benefit from it.