diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes
new file mode 100644
index 000000000..43b1d6258
--- /dev/null
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes
@@ -0,0 +1,3 @@
+# Keep the post-create hook LF so bash inside the dev container doesn't choke on
+# CRLF when the repo is checked out on Windows.
+*.sh text eol=lf
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json
index c0ab20546..f9d80e1ee 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json
@@ -9,7 +9,7 @@
},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
- "postCreateCommand": "pip install --upgrade pip && pip install -r requirements.txt",
+ "postCreateCommand": "bash .devcontainer/post-create.sh",
"customizations": {
"vscode": {
"extensions": [
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh
new file mode 100644
index 000000000..9e29a8141
--- /dev/null
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# =============================================================================
+# Dev container post-create hook (runs once after the container is built).
+#
+# 1. Install the Python dependencies (src/requirements.txt) — ESSENTIAL:
+# every challenge needs these.
+# 2. Best-effort install of the Microsoft ODBC Driver 18 for SQL Server so the
+# OPTIONAL Azure SQL contract-status / renewal tool works out of the box.
+# That tool's connection string uses "Driver={ODBC Driver 18 for SQL
+# Server}" (see labautomation/infra/resources.bicep), so unixODBC alone is
+# not enough. If this step fails (e.g. no network to packages.microsoft.com)
+# the tool simply falls back to the bundled JSON corpus, so we NEVER fail
+# the whole container build over an optional driver.
+# =============================================================================
+set -euo pipefail
+
+echo "==> [1/2] Installing Python dependencies (src/requirements.txt)"
+pip install --upgrade pip
+pip install -r src/requirements.txt
+
+echo "==> [2/2] Installing Microsoft ODBC Driver 18 for SQL Server (optional Azure SQL tool)"
+if command -v odbcinst >/dev/null 2>&1 && odbcinst -q -d 2>/dev/null | grep -q "ODBC Driver 18 for SQL Server"; then
+ echo " Driver already present — skipping."
+else
+ # Debian 12 (bookworm) — matches the python:3.11-bookworm base image.
+ if (
+ set -e
+ curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb
+ sudo dpkg -i packages-microsoft-prod.deb
+ rm -f packages-microsoft-prod.deb
+ sudo apt-get update
+ sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18
+ ); then
+ echo " ODBC Driver 18 installed."
+ else
+ echo " WARN: msodbcsql18 install failed — the Azure SQL tool will use the JSON fallback."
+ fi
+fi
+
+echo "==> post-create complete."
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.dockerignore b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.dockerignore
deleted file mode 100644
index f4d6e7c1c..000000000
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.dockerignore
+++ /dev/null
@@ -1,12 +0,0 @@
-# Keep the MCP server image build context minimal — only requirements.txt and
-# src/ are needed to run the server. Everything else (docs, images, walkthrough,
-# lab automation, screenshots) is excluded.
-*
-!requirements.txt
-!src
-
-# Prune noise re-included with src/
-**/__pycache__
-**/*.pyc
-**/*.pyo
-**/.pytest_cache
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/workflows/ci-eval.yml b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/workflows/ci-eval.yml
index 7adda2df5..4785fa3ca 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/workflows/ci-eval.yml
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/workflows/ci-eval.yml
@@ -45,7 +45,7 @@ jobs:
- name: Install dependencies
if: steps.guard.outputs.configured == 'true'
- run: pip install -r requirements.txt "azure-ai-evaluation[redteam]"
+ run: pip install -r src/requirements.txt "azure-ai-evaluation[redteam]"
- name: Azure login (OIDC)
if: steps.guard.outputs.configured == 'true'
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitignore b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitignore
index 2e7550844..47533eee1 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitignore
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitignore
@@ -1,9 +1,11 @@
# Secrets / local config
+# The repo-root .env is written by src/scripts/write_env.py and holds live
+# secrets (SharePoint app secret, connection strings). Never commit it.
.env
.env.*
!.env.example
-# Python
+# Python (local runs at the repo root)
__pycache__/
*.py[cod]
.venv/
@@ -13,17 +15,6 @@ venv/
.ruff_cache/
.python-version
-# Node / MCP tooling
-node_modules/
-
-# Build / conversion artifacts
-*.log
-*.pdf
-# ...but the Challenge 1 CLM corpus PDFs ARE seed data and must be tracked.
-!src/data/**/*.pdf
-dist/
-build/
-
# OS
.DS_Store
Thumbs.db
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/CODE_OF_CONDUCT.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/CODE_OF_CONDUCT.md
deleted file mode 100644
index 686e5e7a0..000000000
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Microsoft Open Source Code of Conduct
-
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
-
-Resources:
-
-- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
-- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
-- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
-- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support)
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/LICENSE b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/LICENSE
deleted file mode 100644
index cf7bcb2ba..000000000
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 Microsoft Corporation
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md
index 39304f5de..04660f1c4 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md
@@ -300,10 +300,10 @@ challenges are a single story:
## Getting started
1. **Open this repo in Codespaces** (no fork needed — the optional Challenge 6 CI bonus is the only part that needs a fork) — **Code → Codespaces → Create codespace**. The devcontainer installs
- Python 3.11, Azure CLI, `azd`, Node, and `requirements.txt` automatically.
+ Python 3.11, Azure CLI, `azd`, Node, and `src/requirements.txt` automatically.
2. `az login` (and `azd auth login` if you use the `azd up` path)
3. Do **[Challenge 1](challenges/challenge-01.md)** to deploy resources and seed the corpus — provision with
- **`azd up`** (Bicep in `labautomation/infra/`), the **`labautomation/deploy`** script, or the one-click
+ **`azd up`** (run from `src/`; Bicep in `labautomation/infra/`), the **`labautomation/deploy`** script, or the one-click
**Deploy to Azure** button (`infra/azuredeploy.json`). The first two autofill your `.env`.
- **Seeding the corpus — default is Path B** (Challenge 1 · Task 6): **Path B (local-PDF)** needs
no SharePoint and no admin consent, works in every tenant, and builds the `clm-corpus` index —
@@ -317,12 +317,14 @@ challenges are a single story:
```
.
-├── .devcontainer/ # Codespaces definition
-├── azure.yaml # azd config (points at labautomation/infra, write-.env hook)
+├── .devcontainer/ # Codespaces / Dev Containers definition
├── README.md # this file
├── challenges/ # challenge-01 … challenge-06 (one markdown brief per challenge)
├── walkthrough/ # challenge-0N/solution-0N.md — reference solution per challenge
-├── src/ # all source code: agents/, clm_common/, mcp_server/, data/, scripts/ …
+├── src/ # all source code + build/config: agents/, clm_common/, mcp_server/, data/, scripts/ …
+│ ├── azure.yaml # azd config (points at ../labautomation/infra, write-.env hook) — run `cd src && azd up`
+│ ├── Dockerfile # Challenge 4 MCP server image (.dockerignore alongside; build context = src/)
+│ ├── requirements.txt # Python dependencies
│ └── data/ # CLM corpus (PDF contracts/templates/clauses/policies) + eval datasets
├── labautomation/ # infra (Bicep) + deploy, seed corpus/SQL, write .env, smoke test
├── images/ # rendered images + per-challenge screenshots + diagrams
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SECURITY.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SECURITY.md
deleted file mode 100644
index 656f79188..000000000
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SECURITY.md
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-## Security
-
-Microsoft takes the security of our software products and services seriously, which
-includes all source code repositories in our GitHub organizations.
-
-**Please do not report security vulnerabilities through public GitHub issues.**
-
-For security reporting information, locations, contact information, and policies,
-please review the latest guidance for Microsoft repositories at
-[https://aka.ms/SECURITY.md](https://aka.ms/SECURITY.md).
-
-
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SUPPORT.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SUPPORT.md
deleted file mode 100644
index 0cd2ae59f..000000000
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/SUPPORT.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Support
-
-## How to file issues and get help
-
-This project uses **GitHub Issues** to track bugs and feature requests. Please search the
-existing issues before filing new ones to avoid duplicates. For a new issue, file your bug or
-feature request as a new Issue.
-
-For help and questions about running this hackathon:
-
-- Read the top-level [README](README.md) and the per-challenge `README.md` files — each has a
- **🛠️ Troubleshooting** section covering the most common blockers.
-- If you're running this as a facilitated event, ask your **hack coach** first.
-- Otherwise, open a **GitHub Issue** in this repository and tag it `question`.
-
-## Microsoft Support Policy
-
-Support for this hackathon content is limited to the resources listed above.
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md
index f55209e12..c5627509e 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md
@@ -5,7 +5,7 @@
Welcome to your very first challenge! Here you lay the foundation for the whole microhack: you'll
deploy the Azure resources, wire up your development environment, and seed the contract corpus the
later challenges build on. By the end you'll have the full **Microsoft Foundry** environment
-running — with **zero local install** — so the rest of the hack is pure agent-building.
+running in a **prebuilt dev container** — so the rest of the hack is pure agent-building.
If something isn't working as expected, please let your coach know.
@@ -13,8 +13,8 @@ If something isn't working as expected, please let your coach know.
> **📋 Prerequisites:**
> - An **Azure subscription** your lab was provisioned in *(or, if self-hosting, one with rights to create a Foundry project and deploy GPT models)*.
-> - A **GitHub account** (to open the repo in Codespaces).
-> - **GitHub Codespaces** access — everything runs in the browser; no local tooling required.
+> - A **GitHub account** (to clone the repo).
+> - **VS Code** with the **Dev Containers** extension and **Docker Desktop** — the repo's dev container has everything preinstalled *(or **GitHub Codespaces**, if you'd rather run in the browser)*.
> 🧩 **How to use this challenge:** for a MicroHack event your Azure resources are **provisioned for
> you** — you just point your `.env` at them (Task 3) and **confirm you understand what got created**:
@@ -30,8 +30,8 @@ If something isn't working as expected, please let your coach know.
## 🧭 Context
-Everything runs from **GitHub Codespaces** using the devcontainer in this repo (Python 3.11, Azure
-CLI, `azd`, Node). For a MicroHack event the resources below are **already provisioned** into **one
+Everything runs inside the **dev container** in this repo (Python 3.11, Azure
+CLI, `azd`, Node) — open it locally in **VS Code** (Dev Containers) or in **GitHub Codespaces**. For a MicroHack event the resources below are **already provisioned** into **one
resource group** and their endpoints appear on your **lab dashboard**; you copy them into `.env` in
Task 3. *(Self-hosting? One **`azd up`** — Bicep in [`infra/`](../labautomation/infra/) — provisions the
same resource group and autofills `.env`.)*
@@ -128,36 +128,35 @@ text at crawl time); regenerate the PDFs with `python src/scripts/make_corpus_pd
**Before you begin — tick these off:**
-- [ ] You can sign in to [github.com](https://github.com).
- [ ] You can sign in to the [Azure Portal](https://portal.azure.com) with the account your lab was provisioned for (or, if self-hosting, one that can **create resources**).
- [ ] *(Self-hosting only)* Your Azure subscription can deploy **GPT** models (ask your coach if unsure).
- [ ] You have ~30 minutes and a stable connection (provisioning takes 5–10 min on its own).
-### Task 1 · Open the Codespace (~7 min)
+### Task 1 · Open the project in VS Code (~7 min)
-**No fork needed for the main hack (Challenges 1–5)** — the code you run lives in this repo. Open it in **GitHub Codespaces** (a full VS
-Code + terminal in your browser, zero local install); because you work off the source repo, `git pull`
+**No fork needed for the main hack (Challenges 1–5)** — the code you run lives in this repo. Clone it and open it in **VS
+Code** using the **Dev Containers** extension (a prebuilt container with Python, Azure CLI, `azd`, and Node — no manual installs); because you work off the source repo, `git pull`
always gets the latest fixes. *(The **one exception** is the optional **Challenge 6** CI bonus — it runs in **GitHub Actions**, so it needs **your own fork**; you'll create it there, not now.)*
-1. On the repo's GitHub page, click **`< > Code` → Codespaces → Create codespace on `main`**.
- *(Prefer local? `git clone` the repo and **Reopen in Container** with the VS Code Dev Containers
- extension.)*
-2. Wait for the container to build — it installs dependencies with `pip install -r requirements.txt`
+1. Open the folder in **VS Code** (e.g. `code microhack-aiagents`). When VS
+ Code prompts **"Reopen in Container"**, click it — or run **Dev Containers: Reopen in Container** from
+ the Command Palette (**F1**). Requires the **Dev Containers** extension and **Docker Desktop**.
+2. Wait for the container to build — it installs dependencies with `pip install -r src/requirements.txt`
automatically. When the terminal stops scrolling and shows a prompt, it's ready.
-
-
-✅ **You'll know it worked when:** a browser VS Code editor opens with a **Terminal** panel showing a
-ready prompt (e.g. `@your-username ➜ /workspaces/microhack-aiagents (main) $`).
+✅ **You'll know it worked when:** a **VS Code** window opens (locally or in the browser) with a **Terminal** panel showing a
+ready prompt.
> [!NOTE]
-> If Codespaces isn't enabled in your org, see [enabling Codespaces](https://docs.github.com/en/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization) or use a [free personal account](https://github.com/signup) (120 core-hours/month free). While it builds, skim the [scenario & architecture](../README.md#the-scenario--contoso-global) so the pieces you deploy here make sense.
+> **Prefer the browser? Use GitHub Codespaces instead.** On the repo's GitHub page, click **`< > Code` → Codespaces → Create codespace on `main`** — the same dev container builds in the cloud, so you need no local Docker.
+>
+> While it builds, skim the [scenario & architecture](../README.md#the-scenario--contoso-global) so the pieces you deploy here make sense.
---
### Task 2 · Log in to Azure (~3 min)
-Now connect the terminal to your Azure account. In the Codespace **Terminal**, type this and press Enter:
+Now connect the terminal to your Azure account. In the **VS Code Terminal**, type this and press Enter:
```bash
az login --use-device-code
@@ -198,13 +197,13 @@ For a **MicroHack event your Azure resources are already provisioned** — a res
Foundry project, the three-model GPT fleet, and Azure AI Search. You don't deploy anything; you just
point your `.env` at them using the values on your **lab dashboard**.
-**Step 3a — create your `.env`** from the template (Codespace terminal, at the repo root):
+**Step 3a — create your `.env`** from the template (VS Code terminal, at the repo root):
```bash
cp src/.env.example .env
```
-**Step 3b — copy your dashboard values into `.env`.** Open `.env` in the Codespace editor and fill in
+**Step 3b — copy your dashboard values into `.env`.** Open `.env` in the VS Code editor and fill in
the values shown on your lab dashboard:
| Lab dashboard credential | `.env` variable | Example value |
@@ -245,6 +244,7 @@ everything. First pick a region that offers **all three** models — this repo's
> old template is what triggers `DeploymentModelNotSupported` / `ServiceModelDeprecating`.
```bash
+cd src # azure.yaml (the azd project) lives here
azd auth login # separate from az login above
azd up # answer: environment name (e.g. clm-microhack), your subscription, region = Sweden Central
```
@@ -257,7 +257,7 @@ It provisions for **5–10 minutes**, assigns the RBAC roles the later challenge
grounding with `azd env set DEPLOY_BING true` before `azd up`.
> **Prefer not to use `azd`?** `LOCATION=swedencentral ./labautomation/deploy.sh` (add `--with-sql` /
-> `--with-bing`; on Windows outside Codespaces use `./labautomation/deploy.ps1`) provisions the same
+> `--with-bing`; on native **Windows** use `./labautomation/deploy.ps1`) provisions the same
> resources and writes `.env` too. If it fails with `DeploymentModelNotSupported`, a model/version
> isn't offered in your region — see [🛠️ Troubleshooting](#️-troubleshooting).
@@ -286,7 +286,7 @@ your **`clm-project`** → **Models + endpoints**. Confirm the deployments show
>
>
-**4c — Your `.env` file.** In the Codespace file explorer, open **`.env`** at the repo root. Confirm the
+**4c — Your `.env` file.** In the VS Code file explorer, open **`.env`** at the repo root. Confirm the
values are filled in (every entry has a value **except** the `SHAREPOINT_*` corpus and the Challenge 5
`MICROSOFT_APP_*` / `TEAMS_*` variables, which you fill later).
@@ -604,7 +604,7 @@ Smoke test: ✅ PASS
| `Project can only be created under AIServices Kind account with allowProjectManagement set to true` | Fixed in the template (`account.properties.allowProjectManagement: true`). If you hit it, your checkout is behind — run `git pull` and redeploy. |
| SharePoint: *"Tenant does not have a SPO license"*, or you can't grant the app's Graph **admin consent** (only Global Reader / **"Grant admin consent" greyed out**) | Only happens if you're **not** an admin of the tenant — in your own sandbox tenant the Path A script self-grants consent. If you hit it, it's **not** a failure: use the **local-PDF fallback (Path B)** — leave the `SHAREPOINT_*` values blank in `.env` and run `python src/scripts/seed_corpus.py`. It extracts `src/data/**/*.pdf` and populates `clm-corpus` directly (needs the Search Index Data Contributor role, granted during provisioning) — the **same index** the SharePoint path builds, so Challenges 2–6 are unaffected. See [Task 5, Path B](#task-5--seed-the-corpus). |
| `account project create` unavailable | The CLI project command is preview. Create the project in the **Foundry portal**, then set `AZURE_AI_PROJECT_ENDPOINT` in `.env` manually (Overview → Endpoint). |
-| `az login` in Codespaces | Use `az login --use-device-code`. |
+| `az login` in a dev container / Codespaces | Use `az login --use-device-code`. |
| Search / quota errors | Ensure the subscription has quota for Basic Search + the model SKUs; request quota if needed. |
| `PermissionDenied` after deploy | RBAC can take 5–10 min to propagate. Wait, run `az login --use-device-code` again, and retry. |
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md
index 9dd76b121..ad3ec02ee 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md
@@ -272,7 +272,7 @@ agent, your orchestrator) can reach. No editor required.
#### Part A · Host the server on Azure Container Apps
-The server already speaks HTTP — `--http` (what the repo-root [`Dockerfile`](../Dockerfile) runs) serves
+The server already speaks HTTP — `--http` (what the src/ [`Dockerfile`](../src/Dockerfile) runs) serves
**streamable HTTP** at `/mcp` on port 8000. Deploy it (image builds **in the cloud** — no local Docker)
from the **repo root**. The script **reads your `.env`** (the same one the agents use) and
**auto-discovers** the resource group, Foundry account and region from your project endpoint — so there's
@@ -362,12 +362,12 @@ at your URL.
| `clm-mcp` not in *MCP: List Servers* | VS Code discovers a workspace MCP server only from `.vscode/mcp.json` at the **root of the opened folder** — running `python src/mcp_server/server.py` in a terminal does **not** register it. Open the **repo root** (not `src/`) and confirm the file is at `/.vscode/mcp.json`. If it's missing there, **pull the latest hack repo** (older copies shipped it under `src/.vscode/`), then reload VS Code. |
| `Invalid JSON … Internal Server Error` after starting the server | **Harmless.** You typed or pressed **Enter** in the stdio window, so the server rejected the newline as invalid JSON-RPC. It's still running — don't type into it. Use `python src/mcp_server/server.py --list` to confirm the tools without the stdio loop. |
| Orchestrator doesn't route correctly | Sharpen the routing rules in `INSTRUCTIONS`; make each specialist's `as_tool(description=...)` specific. |
-| `ImportError: cannot import name 'Agent' from 'agent_framework'` (or other `agent_framework` import errors) | You have an **old/mismatched build**, or you `pip install`ed into a **different Python** than the one running the script (common with Microsoft Store Python). First see **which** interpreter actually runs the script: `python -c "import sys; print(sys.executable)"`. Then reinstall the pinned deps into **that same** interpreter — the `-U` matters, a plain install won't replace a stale version: `python -m pip install -U -r requirements.txt`. Finally verify: `python -c "import agent_framework as a; print(a.__version__)"` — you need **≥ 1.11.0**. |
+| `ImportError: cannot import name 'Agent' from 'agent_framework'` (or other `agent_framework` import errors) | You have an **old/mismatched build**, or you `pip install`ed into a **different Python** than the one running the script (common with Microsoft Store Python). First see **which** interpreter actually runs the script: `python -c "import sys; print(sys.executable)"`. Then reinstall the pinned deps into **that same** interpreter — the `-U` matters, a plain install won't replace a stale version: `python -m pip install -U -r src/requirements.txt`. Finally verify: `python -c "import agent_framework as a; print(a.__version__)"` — you need **≥ 1.11.0**. |
| MCP server not listed in VS Code | Ensure the MCP feature is enabled and `mcp.json` path is correct; confirm the server imports cleanly first with `python src/mcp_server/server.py --list`. |
| MCP tool call times out | Each call spins up + tears down a Foundry agent (a few seconds). Keep drafts short while testing. |
| `orchestrator_mcp.py` finds no tools / hangs at startup | The stdio server failed to import. Confirm `python src/mcp_server/server.py` starts standalone; `MCPStdioTool` sets `PYTHONPATH=src`, so run from the repo root. |
| Web search tool not attaching | Confirm `AZURE_BING_CONNECTION_NAME` matches a **project connection** for your Grounding with Bing Search resource; run `python src/kb_setup.py` — it prints whether the web-grounding tool built. |
-| `deploy.sh` fails / `az containerapp up` errors | Ensure `az` ≥ 2.53 and the **containerapp** extension (`az extension add -n containerapp`), you're logged in (`az login`) and on the lab subscription (`az account set -s `), and you're running it from the **repo root** (build context needs `Dockerfile`, `requirements.txt`, `src/`). First run also registers the `Microsoft.App`/`Microsoft.OperationalInsights` providers — that can take a minute. |
+| `deploy.sh` fails / `az containerapp up` errors | Ensure `az` ≥ 2.53 and the **containerapp** extension (`az extension add -n containerapp`), you're logged in (`az login`) and on the lab subscription (`az account set -s `), and you're running it from the **repo root** (the script builds from the `src/` context — `src/Dockerfile`, `src/requirements.txt`, app code). First run also registers the `Microsoft.App`/`Microsoft.OperationalInsights` providers — that can take a minute. |
| Foundry agent shows the MCP tool but tool calls fail / time out | Check the app is reachable: open `https://.azurecontainerapps.io/mcp` — it should respond (405/JSON, not a connection error). Confirm ingress is **external** (`az containerapp ingress show`), the URL **ends with `/mcp`**, and the Server URL in Foundry matches exactly. |
| Remote tools return `401/403` / "credential" errors from Foundry | The **container's managed identity** lacks a data-plane role on your Foundry account. Re-run the role step in `deploy.sh` (or assign **Azure AI User** on `FOUNDRY_ACCOUNT_ID`), then wait ~1 min for propagation. Verify with `az containerapp identity show` + `az role assignment list --assignee `. |
| `CLM_MCP_URL` run: connection refused / hangs | Confirm the app is running (`az containerapp show --query properties.runningStatus`) and the URL includes `/mcp`. If you added a key, set `CLM_MCP_KEY` too. Unset `CLM_MCP_URL` to fall back to the local stdio server. |
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md
index bf7777bcf..08c8f4ad2 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md
@@ -7,7 +7,7 @@ network — no local process required.
## What gets deployed
- A single **Azure Container App** named `clm-mcp` (external HTTPS ingress on
- port `8000`) built from the repo‑root [`Dockerfile`](../../Dockerfile).
+ port `8000`) built from the src/ [`Dockerfile`](../../src/Dockerfile).
- The app runs `python src/mcp_server/server.py --http`, exposing the MCP
endpoint at `https://..azurecontainerapps.io/mcp`.
- A **system‑assigned managed identity** granted a data‑plane role on your
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1 b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1
index a87e2853b..7e6374334 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1
@@ -5,7 +5,7 @@
-----------------------------------------------------------------------------
Windows/PowerShell twin of deploy.sh. Builds the image in the cloud (no local
Docker) and prints the /mcp URL a Foundry agent connects to. Run it from the
- REPO ROOT so the build context (requirements.txt + src/) is correct.
+ REPO ROOT; the build context is the src/ folder (Dockerfile + requirements.txt + app code).
ZERO-CONFIG by default: reads your repo-root .env for AZURE_AI_PROJECT_ENDPOINT
+ MODEL_*, then auto-discovers the resource group, Foundry account id and region
@@ -94,7 +94,7 @@ az containerapp up `
--name $AppName `
--resource-group $ResourceGroup `
--location $Location `
- --source . `
+ --source src `
--ingress external `
--target-port 8000 `
--env-vars `
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh
index 3d3562aba..cd99c8f64 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh
@@ -3,8 +3,8 @@
# Challenge 4 · Deploy the CLM MCP server to Azure Container Apps (remote MCP)
# -----------------------------------------------------------------------------
# Builds the image in the cloud (no local Docker needed) and prints the /mcp URL
-# a Foundry agent connects to. Run this from the REPO ROOT so the Docker build
-# context (requirements.txt + src/) is correct.
+# a Foundry agent connects to. Run this from the REPO ROOT; the Docker build
+# context is the src/ folder (Dockerfile + requirements.txt + app code).
#
# ZERO-CONFIG by default: it reads your repo-root `.env` (the same file the
# agents use) for AZURE_AI_PROJECT_ENDPOINT + MODEL_*, then auto-discovers the
@@ -85,7 +85,7 @@ az containerapp up \
--name "$APP_NAME" \
--resource-group "$RESOURCE_GROUP" \
--location "$LOCATION" \
- --source . \
+ --source src \
--ingress external \
--target-port 8000 \
--env-vars \
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md
index efe5bfe1b..cb7a21364 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md
@@ -150,7 +150,7 @@ of the challenge, what "done" looks like, where teams get stuck, and the hint to
- **Watch for:**
- *Orchestrator routes wrong* → sharpen `INSTRUCTIONS` routing rules and make each specialist's
`as_tool(description=...)` specific.
- - *`agent_framework` import error* → `pip install agent-framework-core agent-framework-foundry` (see requirements.txt).
+ - *`agent_framework` import error* → `pip install agent-framework-core agent-framework-foundry` (see src/requirements.txt).
- *MCP server not listed in VS Code* → the workspace config must be at the repo-root `.vscode/mcp.json`
and you must open the **repo root** (not `src/`); confirm the server imports cleanly first (`python src/mcp_server/server.py --list`).
- *(Task 4) Remote deploy* → run `bash deploy/mcp-server/deploy.sh` from the **repo root** (needs the
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md
index 99eec7f91..11c961e5e 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md
@@ -41,7 +41,7 @@ same resources from `infra/`.
[`infra/`](infra/) holds the Bicep templates (plus `azuredeploy.json` for the
one-click **Deploy to Azure** button) that create the Microsoft Foundry project, the
three GPT model deployments (`gpt-5.4`, `gpt-5.6-sol`, `gpt-5.4-nano`), Azure AI Search, Azure SQL, and Application Insights.
-`azure.yaml` at the repo root points `azd` at this folder.
+`src/azure.yaml` points `azd` at this folder (`../labautomation/infra`); run `azd up` from `src/`.
## Scripts
@@ -68,7 +68,7 @@ Seeding, setup & gate scripts — run by participants/coaches during the hack
```bash
az login
-./labautomation/deploy.sh # or: azd up (Windows: labautomation\deploy.ps1)
+./labautomation/deploy.sh # or: (cd src && azd up) (Windows: labautomation\deploy.ps1)
# Default corpus path (own admin tenant): SharePoint app + consent + site + upload + index
python src/scripts/setup_sharepoint_corpus.py
# — or the no-SharePoint fallback: python src/scripts/seed_corpus.py
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json
index 3c1443a4c..866701b15 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json
@@ -3,6 +3,6 @@
"groups": ["M365-E5-Users"],
"deploymentType": "resourcegroup",
"labsPerSubscription": 8,
- "preferredLocation": "swedencentral, westeurope, norwayeast",
- "estimatedDailyCostsUsd": 5.0
+ "preferredLocation": "swedencentral, norwayeast, spaincentral",
+ "estimatedDailyCostsUsd": 15.0
}
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.dockerignore b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.dockerignore
new file mode 100644
index 000000000..faf0b6e39
--- /dev/null
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.dockerignore
@@ -0,0 +1,18 @@
+# The build context IS this src/ folder — it holds the Dockerfile, requirements.txt
+# and all the app code the CLM MCP server needs (Dockerfile does COPY . ./src/).
+# Exclude only caches, local secrets, and build/config files not needed at runtime.
+**/__pycache__
+**/*.pyc
+**/*.pyo
+**/.pytest_cache
+
+# Never bake local secrets into the image
+.env
+.env.*
+!.env.example
+
+# Build/config files the running server image doesn't need
+.gitignore
+.gitattributes
+.dockerignore
+azure.yaml
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitattributes b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.gitattributes
similarity index 100%
rename from 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.gitattributes
rename to 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.gitattributes
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.gitignore b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.gitignore
new file mode 100644
index 000000000..8d31e92ee
--- /dev/null
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/.gitignore
@@ -0,0 +1,33 @@
+# Secrets / local config
+.env
+.env.*
+!.env.example
+
+# Python
+__pycache__/
+*.py[cod]
+.venv/
+venv/
+.pytest_cache/
+.mypy_cache/
+.ruff_cache/
+.python-version
+
+# Node / MCP tooling
+node_modules/
+
+# Build / conversion artifacts
+*.log
+*.pdf
+# ...but the Challenge 1 CLM corpus PDFs ARE seed data and must be tracked.
+!data/**/*.pdf
+dist/
+build/
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Azure / tooling
+.azure/
+*.azd
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/Dockerfile b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/Dockerfile
similarity index 78%
rename from 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/Dockerfile
rename to 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/Dockerfile
index a093187ce..c336799f2 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/Dockerfile
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/Dockerfile
@@ -2,10 +2,11 @@
# =============================================================================
# Challenge 4 — container image for the CLM MCP server (remote / streamable-HTTP)
# -----------------------------------------------------------------------------
-# The build CONTEXT must be the REPO ROOT — the image needs requirements.txt and
-# src/. Deploy it with one command (builds in the cloud, no local Docker needed):
+# The build CONTEXT is this `src/` folder — it holds this Dockerfile,
+# requirements.txt and all the app code (COPY . ./src/). Deploy it with one
+# command (builds in the cloud, no local Docker needed) by pointing at src/:
#
-# az containerapp up -n clm-mcp -g --source . \
+# az containerapp up -n clm-mcp -g --source ./src \
# --target-port 8000 --ingress external \
# --env-vars AZURE_AI_PROJECT_ENDPOINT=
#
@@ -34,7 +35,9 @@ RUN apt-get update \
COPY requirements.txt ./
RUN pip install -r requirements.txt
-COPY src/ ./src/
+# The build context IS this src/ folder, so copy it all into /app/src/ (caches and
+# build/config files are pruned by .dockerignore). CMD below runs src/mcp_server/server.py.
+COPY . ./src/
EXPOSE 8000
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md
index d36686b22..fa6edc843 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md
@@ -21,7 +21,7 @@ helpers live in [`clm_common/`](clm_common/); every entry-point script adds `src
| [`mcp_server/server.py`](mcp_server/server.py) | MCP server exposing the CLM workflow — stdio (local) **and** streamable HTTP (`--http`, for hosting) | 4 |
| [`../.vscode/mcp.json`](../.vscode/mcp.json) | VS Code MCP client config (`clm-mcp`, repo root) | 4 |
| [`orchestrator_mcp.py`](orchestrator_mcp.py) | Orchestrator as MCP client — local stdio, or remote via `CLM_MCP_URL` | 4 |
-| [`../Dockerfile`](../Dockerfile) + [`../deploy/mcp-server/`](../deploy/mcp-server/) | Containerize + deploy the MCP server to Azure Container Apps (remote `/mcp` for Foundry) | 4 |
+| [`Dockerfile`](Dockerfile) + [`../deploy/mcp-server/`](../deploy/mcp-server/) | Containerize + deploy the MCP server to Azure Container Apps (remote `/mcp` for Foundry) | 4 |
| [`manifest/`](manifest/) | Teams / M365 Copilot app package (manifest + icons) | 5 |
| [`red_team.py`](red_team.py) | Automated red-teaming → `redteam_scorecard.json` | 6 |
| [`safety_eval.py`](safety_eval.py) | Safety evaluation + CLM guardrail gate | 6 |
@@ -40,5 +40,5 @@ python src/orchestrator.py # Challenge 4 — orchestration
```
> Config is read from the repo-root `.env` via `clm_common/config.py`. Run
-> [`../labautomation/deploy.sh`](../labautomation/deploy.sh) (or `azd up`) first so the
+> [`../labautomation/deploy.sh`](../labautomation/deploy.sh) (or `cd src && azd up`) first so the
> `.env` is populated and the corpus is seeded.
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/azure.yaml b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/azure.yaml
similarity index 68%
rename from 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/azure.yaml
rename to 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/azure.yaml
index 63091a8ff..5b50484b1 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/azure.yaml
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/azure.yaml
@@ -1,7 +1,8 @@
# ==========================================================================
# Foundry CLM Microhack — Azure Developer CLI (azd) configuration
-# `azd up` provisions the full Foundry environment defined in labautomation/infra/ and then
-# writes a repo-root .env matching labautomation/deploy.sh.
+# This file lives in src/, so run azd from there: `cd src && azd up`.
+# `azd up` provisions the full Foundry environment defined in ../labautomation/infra/
+# and then writes a repo-root .env matching labautomation/deploy.sh.
# Docs: https://aka.ms/azure-dev/schema
# ==========================================================================
name: foundry-clm-microhack
@@ -10,7 +11,7 @@ metadata:
infra:
provider: bicep
- path: labautomation/infra
+ path: ../labautomation/infra
module: main
hooks:
@@ -19,11 +20,11 @@ hooks:
postprovision:
windows:
shell: pwsh
- run: python src/scripts/write_env.py
+ run: python scripts/write_env.py
continueOnError: false
interactive: false
posix:
shell: sh
- run: python3 src/scripts/write_env.py
+ run: python3 scripts/write_env.py
continueOnError: false
interactive: false
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/documents.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/documents.py
index 4307f9a1a..3ee64a827 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/documents.py
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/documents.py
@@ -26,7 +26,7 @@ def read_document_text(path: str | Path) -> str:
except ImportError as exc: # pragma: no cover - dependency hint
raise RuntimeError(
"Reading PDF documents requires 'pypdf'. Install it with "
- "`pip install pypdf` (it is listed in requirements.txt)."
+ "`pip install pypdf` (it is listed in src/requirements.txt)."
) from exc
reader = PdfReader(str(path))
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/red_team.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/red_team.py
index 97983656d..547042f04 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/red_team.py
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/red_team.py
@@ -15,7 +15,7 @@
python src/red_team.py --num-objectives 3 --output redteam_scorecard.json
Requires: `pip install "azure-ai-evaluation[redteam]"` (pulls PyRIT) and an
-Azure AI (Foundry) project + login. See requirements.txt.
+Azure AI (Foundry) project + login. See src/requirements.txt.
"""
from __future__ import annotations
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/requirements.txt b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/requirements.txt
similarity index 100%
rename from 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/requirements.txt
rename to 03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/requirements.txt
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py
index a2f62e177..b2dc3d2d8 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py
@@ -25,7 +25,7 @@
("challenge-0", "02-create-codespace", "GitHub · Create Codespace",
"Code button → Codespaces tab → 'Create codespace on main' green button."),
("challenge-0", "03-codespace-ready", "Codespace · Ready",
- "The VS Code-in-browser Codespace with a terminal open and 'pip install -r requirements.txt' finished."),
+ "The VS Code-in-browser Codespace with a terminal open and 'pip install -r src/requirements.txt' finished."),
("challenge-0", "04-az-login-device", "Azure · Device-code login",
"The https://microsoft.com/devicelogin page where you paste the code printed by 'az login --use-device-code'."),
("challenge-0", "05-azd-up-prompts", "azd up · Prompts",
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/setup_sharepoint_corpus.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/setup_sharepoint_corpus.py
index d443f7114..29bd843b3 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/setup_sharepoint_corpus.py
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/setup_sharepoint_corpus.py
@@ -337,14 +337,15 @@ def upsert_env(values: dict[str, str]) -> None:
print(f" ✓ wrote SHAREPOINT_* values to {ENV_PATH}")
# Best-effort: mirror into the azd environment so a later `azd provision`/`azd up`
- # regenerates .env with these values instead of blanking them.
+ # regenerates .env with these values instead of blanking them. azure.yaml lives
+ # in src/, so run azd from there.
azd = shutil.which("azd")
if azd:
for key in SHAREPOINT_KEYS:
if key in values:
subprocess.run(
[azd, "env", "set", key, values[key]],
- cwd=REPO_ROOT, capture_output=True, text=True, check=False,
+ cwd=REPO_ROOT / "src", capture_output=True, text=True, check=False,
)
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/write_env.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/write_env.py
index 23179dd18..b0c9ed763 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/write_env.py
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/write_env.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Write the repo-root .env from deployment outputs.
-By default this runs as the azd `postprovision` hook (see azure.yaml) and reads
+By default this runs as the azd `postprovision` hook (see src/azure.yaml) and reads
the provisioned values via `azd env get-values`. Pass `--deployment ` to
instead read the outputs of a subscription-scoped ARM deployment (the plain-ARM
/ "Deploy to Azure" path using infra/azuredeploy.json), e.g.:
@@ -21,6 +21,8 @@
REPO_ROOT = Path(__file__).resolve().parents[2]
ENV_PATH = REPO_ROOT / ".env"
+# azure.yaml (the azd project) lives in src/, so azd commands must run from there.
+AZD_DIR = REPO_ROOT / "src"
# Keys sourced from Bicep outputs (via azd env). Fall back to the src/.env.example
# defaults for the constant-valued ones if an output is missing.
@@ -40,12 +42,12 @@
}
-def _run(cmd: list[str]) -> str:
- """Run a command in the repo root, exiting with a friendly message on error."""
+def _run(cmd: list[str], cwd: Path = REPO_ROOT) -> str:
+ """Run a command (default cwd = repo root), exiting with a friendly message on error."""
try:
proc = subprocess.run(
cmd,
- cwd=REPO_ROOT,
+ cwd=cwd,
capture_output=True,
text=True,
check=True,
@@ -59,7 +61,7 @@ def _run(cmd: list[str]) -> str:
def azd_env_values() -> dict[str, str]:
"""Return azd environment values as a dict (parsing KEY="value" lines)."""
- stdout = _run(["azd", "env", "get-values"])
+ stdout = _run(["azd", "env", "get-values"], cwd=AZD_DIR)
values: dict[str, str] = {}
for line in stdout.splitlines():
line = line.strip()
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md
index 302fa5d45..f38d5040c 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md
@@ -27,7 +27,7 @@ contract corpus the later challenges ground on.
## 🛠️ Task-by-task walkthrough
### Task 1 · Open the Codespace
-No fork — the code lives in this repo. **`< > Code` ▸ Codespaces ▸ Create codespace on `main`**; the devcontainer builds and `pip install -r requirements.txt` runs automatically. *(Local alt: `git clone` then **Reopen in Container**.)*
+No fork — the code lives in this repo. **`< > Code` ▸ Codespaces ▸ Create codespace on `main`**; the devcontainer builds and `pip install -r src/requirements.txt` runs automatically. *(Local alt: `git clone` then **Reopen in Container**.)*
> 📸 **Screenshot slot:** creating the **Codespace**.
>
@@ -63,9 +63,10 @@ The model names + `AZURE_SEARCH_INDEX` (`clm-corpus`) / `AZURE_SEARCH_CONNECTION
Self-hosting? One azd up provisions everything and writes .env for you
```bash
+cd src # azure.yaml (the azd project) lives here
azd auth login # separate from az login above
azd up # env name, subscription, region = Sweden Central (offers all three models)
-# — or the scripted path — LOCATION=swedencentral ./labautomation/deploy.sh (deploy.ps1 on Windows)
+# — or the scripted path — LOCATION=swedencentral ./labautomation/deploy.sh (deploy.ps1 on Windows; from repo root)
```
The `postprovision` hook → [`src/scripts/write_env.py`](../../src/scripts/write_env.py) reads the deployment outputs (`azd env get-values`, or `--deployment` for the ARM path) and writes every env var the agents use — filling constants from a `DEFAULTS` map when an output is absent, so you can skip the manual paste above:
```python
diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md
index f067def15..0ce50b6a5 100644
--- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md
+++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md
@@ -17,7 +17,7 @@ called from a **Foundry** agent by URL.
`draft_contract` · `analyze_contract` · `get_contract_status` over **stdio** (local) and
**streamable HTTP** (`--http`, for hosting); VS Code loads the stdio server from
[`.vscode/mcp.json`](../../.vscode/mcp.json) (repo root).
-- [`Dockerfile`](../../Dockerfile) + [`deploy/mcp-server/deploy.sh`](../../deploy/mcp-server/deploy.sh)
+- [`Dockerfile`](../../src/Dockerfile) + [`deploy/mcp-server/deploy.sh`](../../deploy/mcp-server/deploy.sh)
host it on **Azure Container Apps** as a remote `https://…/mcp` endpoint a Foundry agent can call.
- [`src/orchestrator_mcp.py`](../../src/orchestrator_mcp.py) runs the same GPT-5.4
orchestrator as an **MCP client** — local stdio, or the remote server via `CLM_MCP_URL`.
@@ -118,9 +118,9 @@ spawns the stdio server for you (no IDE) and runs draft → analyze → status o
start **clm-mcp** → call `#analyze_contract` in Copilot Chat.
### Task 4 · Host it remotely + call it from Foundry
-**Part A — host on Azure Container Apps.** The repo-root [`Dockerfile`](../../Dockerfile) runs
+**Part A — host on Azure Container Apps.** The src/ [`Dockerfile`](../../src/Dockerfile) runs
`server.py --http` (streamable HTTP at `/mcp`). Deploy from the repo root — the image builds in the
-cloud, no local Docker:
+cloud (from the `src/` context), no local Docker:
```bash
bash deploy/mcp-server/deploy.sh # reads .env, auto-discovers RG/account/region → https://clm-mcp..azurecontainerapps.io/mcp
```
@@ -174,7 +174,7 @@ propagation takes ~1 min, so just re-run the script or wait if a later model cal
| [`src/mcp_server/server.py`](../../src/mcp_server/server.py) | MCP server exposing the CLM workflow (stdio **and** streamable HTTP via `--http`) |
| [`.vscode/mcp.json`](../../.vscode/mcp.json) | VS Code MCP client config (`clm-mcp`, repo root) |
| [`src/orchestrator_mcp.py`](../../src/orchestrator_mcp.py) | Orchestrator as MCP client — local stdio, or remote via `CLM_MCP_URL` |
-| [`Dockerfile`](../../Dockerfile) + [`deploy/mcp-server/deploy.sh`](../../deploy/mcp-server/deploy.sh) | Containerize + deploy the server to Azure Container Apps as a remote `/mcp` endpoint |
+| [`Dockerfile`](../../src/Dockerfile) + [`deploy/mcp-server/deploy.sh`](../../deploy/mcp-server/deploy.sh) | Containerize + deploy the server to Azure Container Apps as a remote `/mcp` endpoint |
## Run it