Skip to content
← Back to blog

Anatomy of an Agent Lifecycle Repo — Bicep, Configs, and the Deploy Script

2026-07-034 min readAzure, AI, DevOps, CI/CD

Chapter 1 made the argument: agents are configuration, so treat them like infrastructure. This chapter opens the hood on Eric Chansen's foundry-agents-lifecycle repo (all credit to that project for these patterns) and walks the four building blocks every agent-lifecycle repo needs — whatever you end up naming the folders in yours.

The layout, at a glance

Trimmed to the load-bearing parts:

foundry-agents-lifecycle/
├── infra/                           # Bicep: Foundry account, project, models, Key Vault
│   ├── main.bicep
│   ├── modules/
│   └── environments/                # dev/test/prod parameter files
├── src/
│   ├── agent/
│   │   ├── agent_definition.py      # config + prompt + tools → SDK parameters
│   │   ├── prompts/
│   │   │   ├── system_prompt.md     # THE prompt, as reviewable markdown
│   │   │   └── system_prompt.prod.md# optional prod override
│   │   └── tools/                   # function tools as plain Python
│   ├── scripts/
│   │   ├── deploy_agent.py          # create/update the agent via SDK
│   │   ├── run_evaluation.py        # the quality gate (chapter 3)
│   │   └── teardown_agent.py
│   └── tests/unit/                  # validate configs before touching Azure
├── config/
│   ├── agent-config.dev.json
│   ├── agent-config.test.json
│   └── agent-config.prod.json
├── .github/workflows/               # ci.yml + cd.yml        (chapter 3)
└── .azdo/pipelines/                 # the same, ADO-flavored  (chapter 4)

Notice what this structure encodes: one agent definition, one prompt (with an optional prod override), N environment configs. The environments differ in parameters, never in logic. That's the drift-killer.

Block 1 — Infrastructure as Bicep

Before an agent can exist, its house must: a Foundry account, a project, model deployments, and a Key Vault. The repo provisions all of it from infra/ with per-environment parameter files, so "stand up a complete test environment" is one command:

az deployment sub create \
  --location eastus2 \
  --template-file infra/deploy-infra.bicep \
  --parameters environment=dev pipelineSource=github

(Or azd up if you're an Azure Developer CLI person — the repo supports both.)

The part I want you to steal even if you steal nothing else: model deployments are IaC too (modules/model-deployments.bicep). Dev gets a mini model at low capacity; prod gets the production model at 5× the throughput — declared in parameters, not clicked in a portal. When a new model generation lands, changing prod's model is a parameter-file PR: reviewed, evaluated (chapter 3), and rollback-able.

One honest caveat from the README worth repeating: the subscription-level deployment creates resource groups, which needs Contributor at subscription scope. In locked-down enterprises you'll likely get a pre-created resource group instead — the repo ships a resource-group-level entry point (main.bicep) for exactly that situation.

Block 2 — Per-environment configs

The heart of the promotion story is three small JSON files:

// config/agent-config.dev.json  (abridged)
{
  "agent_name": "demo-agent-dev",
  "model": "gpt-4o-mini",
  "prompt_file": "system_prompt.md"
}

Prod's file points at the bigger model and, optionally, a different prompt file. The differences across the repo's three environments, in one table:

| | DEV | TEST | PROD | |---|---|---|---| | Model | mini | production | production | | Capacity | 10K TPM | 20K TPM | 50K TPM | | Prompt | standard | standard | prod override | | Eval threshold | 3.0 | 3.5 | 4.0 | | Approval | none | 1 approver | 2+ approvers |

Everything a stakeholder ever asks about environment differences is answered by files in git. No tribal knowledge, no portal archaeology.

Block 3 — Prompts as markdown files

system_prompt.md being a standalone file, not a string constant buried in Python, is a quietly excellent decision. Prompt PRs get proper markdown diffs that a non-developer domain expert can review. Repo-wide search finds the prompt. And the optional system_prompt.prod.md override handles the real-world case where prod needs stricter language (compliance lines, escalation rules) without forking the whole agent.

Treat your system prompt like source code, because — chapter 1's lesson — it is the source code.

Block 4 — The deploy script

src/scripts/deploy_agent.py is where configuration becomes a live agent. Its logic is deliberately boring:

  1. Load config/agent-config.{env}.json.
  2. Read the prompt file it references.
  3. Resolve tool definitions from src/agent/tools/.
  4. Build a PromptAgentDefinition (the azure-ai-projects 2.x SDK — same one from my Build Your First AI App series).
  5. Connect to the target environment's Foundry project and call agents.create_version(...).

That last call carries the whole deployment philosophy: create_version is idempotent-by-versioning. New agent? Created. Existing agent? A new numbered version. Every pipeline run leaves an inspectable trail in Foundry, and "roll back" means redeploying a previous git commit — versions line up on both sides.

agent = project.agents.create_version(
    agent_name=config["agent_name"],
    definition=PromptAgentDefinition(
        model=config["model"],
        instructions=prompt_text,
        tools=tools,
    ),
)

The script also supports --dry-run — build everything, call nothing. File that away; it becomes the cheapest, fastest check in the CI pipeline next chapter.

Auth, by the way, is DefaultAzureCredential everywhere: your az login locally, workload identity in pipelines. No keys in the repo, no keys in the pipeline. Secrets that must exist (there are few) live in the Bicep-provisioned Key Vault.

Unit tests for agents?

Yes — and they're not testing the LLM. src/tests/unit/ validates the machinery: configs parse and contain required fields, referenced prompt files exist, agent names satisfy Foundry's rules, tool schemas are well-formed. Five-second tests that catch the classic "renamed the prompt file, forgot the config" break before it wastes a deployment. LLM behavior is tested by evaluations, which get the spotlight next chapter.

Wrapping up

Four blocks: Bicep for the house, JSON configs for per-environment identity, markdown prompts for reviewability, and a thin SDK script that applies desired state with create_version. Nothing exotic — which is exactly why it works.

Next — Chapter 3: the pipelines themselves. GitHub Actions CI that blocks bad PRs, OIDC federation with zero stored credentials, and the evaluation gates that decide whether an agent is allowed to reach production.

Praveen Anil

Praveen Anil

Infrastructure Lead · Azure & AI · About me