From 9497be78ed679c119655c6aa598f335790a22d5b Mon Sep 17 00:00:00 2001 From: Paul Yuk Date: Tue, 16 Jun 2026 17:11:37 -0700 Subject: [PATCH 1/6] Migrate quickstart from Functions Core Tools v4 to Functions CLI v5 (func5) Adopt the new Azure Functions CLI v5 preview, installed alongside any existing v4 as 'func5'. This shrinks the local-dev story from three terminals to two and removes the separate Azurite install/start. What changed - README Prerequisites: drop 'Core Tools v4 >= 4.12.0 (v5 preview not yet compatible)'. Recommend Functions CLI v5 installed as 'func5', with a one-line nod to v4 users (full fallback in troubleshooting). - README Quickstart step 1: replace 'brew install azure-functions-core-tools@4 + npm install -g azurite' with the download-from-releases install pattern and a 'func5 setup --features python' one-shot that pulls host + python-worker + python-templates + the extension-bundles workload (the fix for #5309 on early v5 previews). - README Quickstart step 3: collapse three terminals to two. v5 auto-starts Azurite, so the dedicated 'azurite ...' terminal goes away. - docs/troubleshooting.md: invert the v5 advice. Remove the 'Stay on v4' note. Replace it with a self-fix entry that points at the extension bundles workload. Drop the dead Azurite 'Connection refused 127.0.0.1:10000' and 'InvalidHeaderValue' entries (v5 manages Azurite for you). Add a new 'Still using v4' section that documents how to keep the v4 path working until v5 reaches GA. - chat.py, function_app.py, docs/configuration.md, infra/scripts: every user-visible 'uv run func start' becomes 'uv run func5 start', and the hydrate scripts now hint at the v5 auto-Azurite behavior. Why The repo currently tells users to install Core Tools v4 + Azurite + the host runtime, and routes around an extension-bundle bug in early v5 previews. v5-preview.2 ships the extension bundles workload (4.42 +) and resolves that bug; staying on v4 also means the user has to maintain a separate Azurite install and run it in its own terminal. Installing v5 as 'func5' avoids breaking any consumer that still needs v4 'func'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 35 ++++++++++++++++-------- chat.py | 15 +++++----- docs/configuration.md | 2 +- docs/troubleshooting.md | 22 ++++++++++----- function_app.py | 2 +- infra/scripts/authorize-connectors.ps1 | 2 +- infra/scripts/authorize-connectors.sh | 2 +- infra/scripts/hydrate-local-settings.ps1 | 7 ++--- infra/scripts/hydrate-local-settings.sh | 7 ++--- 9 files changed, 56 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 1e00cbc..c8297e7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Run it locally in minutes against sample data, point it at your real inbox, then ## Prerequisites - Python 3.13+. Easiest install: [uv](https://docs.astral.sh/uv/), then `uv python install 3.13`. -- [Azure Functions Core Tools v4](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local) ≥ 4.12.0 (the v5 preview is not yet compatible). +- [Azure Functions CLI v5 preview](https://learn.microsoft.com/en-us/azure/azure-functions/functions-cli-develop-local), installed as `func5` (Quickstart step 1). v5 auto-starts Azurite and ships extension bundles as a workload, so this template runs with two terminals instead of three. Already on v4? Keep it; the install below leaves `func` alone and adds `func5` alongside. See [Troubleshooting: still using v4](docs/troubleshooting.md#still-using-v4) for the fallback. - [Azure Developer CLI (`azd`)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/). - An Azure subscription. `azd provision` (Quickstart step 2) creates the Microsoft Foundry model deployment the agents need - required even for the offline path. - For real M365 (or `azd up`): permission to authorize Microsoft 365 connectors, plus the `connector-namespace` CLI extension: @@ -40,17 +40,31 @@ Five steps: install, get resources, run locally, try it, deploy. ### 1. Install the tools -**macOS:** +The Azure Functions CLI v5 is in preview. Install the latest preview build from the [Azure Functions Core Tools releases page](https://github.com/Azure/azure-functions-core-tools/releases) (look for `Azure.Functions.Cli-osx-arm64-5.*.tar.gz` on Apple Silicon, `-osx-x64-` on Intel, `-linux-x64-` on Linux, or `-win-x64-` on Windows). Extract it into its own directory and put a `func5` shim on your `PATH` so v5 sits alongside any existing v4 `func` install. + +**macOS / Linux:** ```bash -brew tap azure/functions -brew install azure-functions-core-tools@4 brew install azure-dev -npm install -g azurite curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install Functions CLI v5 as func5 (leaves any existing func/v4 alone) +FUNC5_DIR="$HOME/.azure-functions/v5" +mkdir -p "$FUNC5_DIR" && cd "$FUNC5_DIR" +# Replace the URL with the latest v5 preview asset for your OS/arch from the releases page above +curl -LO https://github.com/Azure/azure-functions-core-tools/releases/download/v5.0.0-preview.2/Azure.Functions.Cli-osx-arm64-5.0.0-preview.2.tar.gz +tar -xzf Azure.Functions.Cli-osx-arm64-5.0.0-preview.2.tar.gz +mkdir -p "$HOME/.local/bin" && ln -sf "$FUNC5_DIR/func" "$HOME/.local/bin/func5" +# add ~/.local/bin to PATH if it isn't already, then in a fresh shell: +func5 setup --features python +``` + +**Windows:** +Install [azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) and [uv](https://docs.astral.sh/uv/getting-started/installation/), then download the v5 preview `Azure.Functions.Cli-win-x64-5.*.zip` from the releases page, extract it to a folder like `C:\func5`, add that folder to your `PATH` as `func5` (rename `func.exe` to `func5.exe` if you want both versions side-by-side), and run: +```powershell +func5 setup --features python ``` -**Linux / Windows / WSL:** -Use the [Core Tools v4 install guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools), [azd install guide](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd), [Azurite install guide](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite), and [uv install guide](https://docs.astral.sh/uv/getting-started/installation/). +`func5 setup --features python` is a one-shot install of the v5 host, Python worker, templates, and the extension bundles workload (the fix for [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309)). You only need to run it once per machine. ### 2. Get the resources you need @@ -73,14 +87,13 @@ azd provision ### 3. Run the local client ```bash -azurite --silent --skipApiVersionCheck --location .azurite # terminal A -uv run func start # terminal B -uv run python chat.py # terminal C +uv run func5 start # terminal A (v5 auto-starts Azurite) +uv run python chat.py # terminal B ``` (These commands work identically on macOS, Linux, and Windows.) -> 🪟 **Windows local dev:** If `uv run func start` fails with `ModuleNotFoundError: No module named 'azure_functions_agents'`, the Microsoft Store python.exe alias on your `PATH` is shadowing the venv Python. See [Troubleshooting: Windows local dev](docs/troubleshooting.md#windows-local-dev) for the fix - you'll need to remove the Store Python and disable App execution aliases in Windows Settings. +> 🪟 **Windows local dev:** If `uv run func5 start` fails with `ModuleNotFoundError: No module named 'azure_functions_agents'`, the Microsoft Store python.exe alias on your `PATH` is shadowing the venv Python. See [Troubleshooting: Windows local dev](docs/troubleshooting.md#windows-local-dev) for the fix - you'll need to remove the Store Python and disable App execution aliases in Windows Settings. ### 4. Try it (offline, safe) diff --git a/chat.py b/chat.py index 5e6cb3b..a080290 100644 --- a/chat.py +++ b/chat.py @@ -153,10 +153,9 @@ def _print_host_unreachable_hint() -> None: print(" or check the Function App is running in the Azure portal.") return print(" The local Functions host is not running. Either:") - print(" A) Run it locally (3 terminals):") - print(" terminal A: azurite --silent --location ~/.azurite") - print(" terminal B: uv run func start") - print(" terminal C: uv run python chat.py") + print(" A) Run it locally (2 terminals; v5 auto-starts Azurite):") + print(" terminal A: uv run func5 start") + print(" terminal B: uv run python chat.py") print(" B) Or call the deployed cloud Function App from this shell:") print(" source ./infra/scripts/use-cloud-host.sh") print(" uv run python chat.py") @@ -622,7 +621,7 @@ def _render_result(agent_name: str, result: dict, elapsed: float) -> None: print(" - Teams post with empty TEAMS_TEAM_ID / TEAMS_CHANNEL_ID") print(" - 3 consecutive failures trip the runtime's circuit breaker, which") print(" stops further tool calls but still returns a (partial) summary.") - print(" Run `uv run func start --verbose` to see the exact connector error.") + print(" Run `uv run func5 start --verbose` to see the exact connector error.") live_blocked = mode != "dry_run" and re.search( r"could not read|forbidden|unauthorized|not authoriz", response_text, re.IGNORECASE @@ -662,7 +661,7 @@ def trigger_agent(agent_name: str, mode_icon: str, mode_label: str = "") -> None print(" Check that:") print(" - the .agent.md frontmatter has builtin_endpoints.chat_api: true") print(" - host.json extensions.http.routePrefix matches (this client reads it)") - print(" - you restarted `uv run func start` after editing either file") + print(" - you restarted `uv run func5 start` after editing either file") if details: print(f" {details}") print() @@ -727,7 +726,7 @@ def mark(key: str) -> str: print(" Next step to LIVE: `azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com`,") print(" then `./infra/scripts/hydrate-local-settings.sh`. If you have not already") print(" authorized the Outlook connection, also run (one time)") - print(" `./infra/scripts/authorize-connectors.sh`, then restart `uv run func start`.") + print(" `./infra/scripts/authorize-connectors.sh`, then restart `uv run func5 start`.") elif not _teams_alerts_enabled(): print(" Outlook is LIVE. Set TEAMS_TEAM_ID / TEAMS_CHANNEL_ID to enable Teams alerts") print(" (inbox escalations and the daily briefing's urgent post).") @@ -921,7 +920,7 @@ def chat_with_inbox() -> None: if exc.code == 404: print(f" No agent responded at {chat_url(CHAT_AGENT[0])}") print(" Check inbox-chat.agent.md has builtin_endpoints.chat_api: true and") - print(" that you restarted `uv run func start` after adding it.") + print(" that you restarted `uv run func5 start` after adding it.") if details: print(f" {details}") print() diff --git a/docs/configuration.md b/docs/configuration.md index f0370de..5505ab1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ Run the agents against your real Outlook + Teams while still developing locally: azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com ./infra/scripts/hydrate-local-settings.sh ./infra/scripts/authorize-connectors.sh -# Ctrl-C the func host, then `uv run func start` again +# Ctrl-C the func host, then `uv run func5 start` again ``` `authorize-connectors.sh` is **required** the first time you go live: it runs a one-time OAuth consent so the connector namespace can read your mailbox and send on your behalf. It opens a browser tab per connection and waits until the connection reports `Connected`. Setting the env vars alone is not enough. Until consent completes, LIVE runs fail with `could not read inbox` (the agent stops and sends nothing). Re-running the script is safe; already-authorized connections are skipped. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 72a936e..66a95ce 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -5,18 +5,26 @@ ## Local setup - **`Port 7071 is unavailable`**. Another `func` is still running. `lsof -nP -iTCP:7071 -sTCP:LISTEN` to find the PID, then `kill `. -- **`ModuleNotFoundError: agent_functions`**. Core Tools picked a Python worker that can't see the venv. Always start with `uv run func start`, not bare `func start`. `uv run` prepends `.venv/bin` so the 3.13 worker is selected. -- **`Connection refused 127.0.0.1:10000`**. Azurite isn't running. Start it in another terminal. -- **`InvalidHeaderValue` + `The API version ... is not supported by Azurite`**. Your SDK is using a newer Storage API than Azurite supports. Upgrade Azurite and start it with `azurite --silent --skipApiVersionCheck --location .azurite`. -- **`No installed bundle workload satisfies Microsoft.Azure.Functions.ExtensionBundle.Preview`**. You're on Core Tools v5 preview. v5 can't load the Preview bundle yet ([tracking issue #5309](https://github.com/Azure/azure-functions-core-tools/issues/5309)). Stay on v4. -- **Worker exits with SIGTERM 143 on startup**. Core Tools < 4.12.0 ships only a Python 3.12 worker. `brew upgrade azure-functions-core-tools@4` to ≥ 4.12.0. +- **`ModuleNotFoundError: agent_functions`**. The Functions host picked a Python worker that can't see the venv. Always start with `uv run func5 start`, not bare `func5 start`. `uv run` prepends `.venv/bin` so the 3.13 worker is selected. +- **Worker exits with SIGTERM 143 on startup**. The Python worker on your machine is older than 3.13. Re-run `func5 setup --features python` to pick up the current preview worker. +- **`No installed bundle workload satisfies Microsoft.Azure.Functions.ExtensionBundle.Preview`**. The extension bundles workload is missing from your v5 install. Run `func5 workload install azure.functions.cli.workloads.extensionbundles` (or `func5 setup --features python`, which installs it as part of the bundle). This was [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309) on early v5 previews and is fixed in `bundles@4.42.0-preview.2` and later. - **Live mode: `403 Forbidden` from MCP**. The connector connection isn't authorized for the signed-in identity. Re-run `./infra/scripts/authorize-connectors.sh` and complete the browser consent for both Outlook and Teams. - **Live mode: agent returns `could not read inbox`**. The Outlook connection has not completed OAuth consent (its status is not `Connected`). Run `./infra/scripts/authorize-connectors.sh`, finish the browser consent as the mailbox owner, wait for `Connected`, then retry. This is a one-time step per connection; env vars alone do not authorize it. - **Windows PowerShell hydrate**. Use `pwsh -File ./infra/scripts/hydrate-local-settings.ps1` (skips ExecutionPolicy without `Set-ExecutionPolicy`). +## Still using v4 + +If you have an existing Azure Functions Core Tools v4 install (`brew install azure-functions-core-tools@4` on macOS, or the v4 installer on Windows / Linux) and don't want to add v5 yet, you can still run this template: + +- Substitute `func` for `func5` everywhere in this repo (`uv run func start`, etc.). +- Start Azurite yourself in a separate terminal before `func start`: `azurite --silent --skipApiVersionCheck --location .azurite`. v4 does not auto-manage it. +- Make sure your Core Tools is ≥ 4.12.0 so the Python 3.13 worker ships. + +The v5 path is recommended because it removes the Azurite terminal and ships the extension bundles workload that this template depends on. Once v5 reaches GA, this fallback section goes away. + ## Windows local dev -**Symptom:** `uv run func start` or `func start` fails with: +**Symptom:** `uv run func5 start` or `func5 start` fails with: ``` ModuleNotFoundError: No module named 'azure_functions_agents' ``` @@ -55,7 +63,7 @@ ModuleNotFoundError: No module named 'pydantic_core._pydantic_core' 4. **Retry:** ```powershell - uv run func start + uv run func5 start ``` **Note on `hydrate-local-settings.ps1`:** The provisioning script can also set `languageWorkers__python__defaultExecutablePath` to point at your venv Python, which helps — but this mitigation is **not sufficient alone**. The OS-level `PATH` search still happens first, so you must still remove the Store Python and disable aliases. Both steps together ensure the 3.13 worker loads correctly. diff --git a/function_app.py b/function_app.py index 07ac3f4..63732e2 100644 --- a/function_app.py +++ b/function_app.py @@ -73,7 +73,7 @@ def _resolve(key: str) -> str: banner.append("Fix: edit local.settings.json, or run:") for key, _ in issues: banner.append(f" azd env set {key} ") - banner.append("Then re-run `./infra/scripts/hydrate-local-settings.sh` and restart `uv run func start`.") + banner.append("Then re-run `./infra/scripts/hydrate-local-settings.sh` and restart `uv run func5 start`.") banner.append("=" * 78) print("\n".join(banner), file=sys.stderr, flush=True) diff --git a/infra/scripts/authorize-connectors.ps1 b/infra/scripts/authorize-connectors.ps1 index 3275685..f35e1f0 100644 --- a/infra/scripts/authorize-connectors.ps1 +++ b/infra/scripts/authorize-connectors.ps1 @@ -4,7 +4,7 @@ # # Safe to re-run: already-authorized connections are skipped. # Works without a deployed function app. Runs against the connector namespace -# directly so local `uv run func start` can call real MCP tools. +# directly so local `uv run func5 start` can call real MCP tools. # # Prereq: the `connector-namespace` Azure CLI extension. Install once with: # az extension add --source Date: Tue, 16 Jun 2026 17:13:01 -0700 Subject: [PATCH 2/6] Use real v5 preview asset names (func-osx-arm64.tar.gz, etc.) The earlier URL (Azure.Functions.Cli-osx-arm64-5.0.0-preview.2.tar.gz) 404s. The actual asset names on the v5 preview releases are func-{osx|linux|win}-{x64|arm64}.{tar.gz|zip}. Verified all six exist. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c8297e7..2ce9af4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Five steps: install, get resources, run locally, try it, deploy. ### 1. Install the tools -The Azure Functions CLI v5 is in preview. Install the latest preview build from the [Azure Functions Core Tools releases page](https://github.com/Azure/azure-functions-core-tools/releases) (look for `Azure.Functions.Cli-osx-arm64-5.*.tar.gz` on Apple Silicon, `-osx-x64-` on Intel, `-linux-x64-` on Linux, or `-win-x64-` on Windows). Extract it into its own directory and put a `func5` shim on your `PATH` so v5 sits alongside any existing v4 `func` install. +The Azure Functions CLI v5 is in preview. Install the latest preview build from the [Azure Functions Core Tools releases page](https://github.com/Azure/azure-functions-core-tools/releases) (look for `func-osx-arm64.tar.gz` on Apple Silicon, `func-osx-x64.tar.gz` on Intel, `func-linux-x64.tar.gz` / `func-linux-arm64.tar.gz` on Linux, or `func-win-x64.zip` / `func-win-arm64.zip` on Windows). Extract it into its own directory and put a `func5` shim on your `PATH` so v5 sits alongside any existing v4 `func` install. **macOS / Linux:** ```bash @@ -50,16 +50,17 @@ curl -LsSf https://astral.sh/uv/install.sh | sh # Install Functions CLI v5 as func5 (leaves any existing func/v4 alone) FUNC5_DIR="$HOME/.azure-functions/v5" mkdir -p "$FUNC5_DIR" && cd "$FUNC5_DIR" -# Replace the URL with the latest v5 preview asset for your OS/arch from the releases page above -curl -LO https://github.com/Azure/azure-functions-core-tools/releases/download/v5.0.0-preview.2/Azure.Functions.Cli-osx-arm64-5.0.0-preview.2.tar.gz -tar -xzf Azure.Functions.Cli-osx-arm64-5.0.0-preview.2.tar.gz +# Swap in the asset for your OS/arch and the latest preview tag from the releases page above +curl -L -o func5.tar.gz \ + https://github.com/Azure/azure-functions-core-tools/releases/download/v5.0.0-preview.2/func-osx-arm64.tar.gz +tar -xzf func5.tar.gz && rm func5.tar.gz mkdir -p "$HOME/.local/bin" && ln -sf "$FUNC5_DIR/func" "$HOME/.local/bin/func5" -# add ~/.local/bin to PATH if it isn't already, then in a fresh shell: +# Add ~/.local/bin to PATH if it isn't already, then in a fresh shell: func5 setup --features python ``` **Windows:** -Install [azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) and [uv](https://docs.astral.sh/uv/getting-started/installation/), then download the v5 preview `Azure.Functions.Cli-win-x64-5.*.zip` from the releases page, extract it to a folder like `C:\func5`, add that folder to your `PATH` as `func5` (rename `func.exe` to `func5.exe` if you want both versions side-by-side), and run: +Install [azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) and [uv](https://docs.astral.sh/uv/getting-started/installation/), then download the v5 preview `func-win-x64.zip` (or `func-win-arm64.zip`) from the releases page, extract it to a folder like `C:\func5`, rename `func.exe` to `func5.exe` so it sits alongside any existing v4 `func.exe`, add `C:\func5` to your `PATH`, and run: ```powershell func5 setup --features python ``` From b597176645d5669d7972a20a2f5075a5c4f6eedd Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 17:41:08 -0700 Subject: [PATCH 3/6] Slim README install steps: link to upstream docs, keep only the func5 workload callout Drops OS-specific brew/curl/PowerShell snippets and the FUNC5_DIR archive recipe in favor of links to the official install docs for uv, azd, func5, and the connector-namespace CLI extension. The only inline command kept is `func5 setup --features python` since the extension bundles workload is the one template-specific, non-default thing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 2ce9af4..b64ce5e 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,11 @@ Run it locally in minutes against sample data, point it at your real inbox, then ## Prerequisites -- Python 3.13+. Easiest install: [uv](https://docs.astral.sh/uv/), then `uv python install 3.13`. -- [Azure Functions CLI v5 preview](https://learn.microsoft.com/en-us/azure/azure-functions/functions-cli-develop-local), installed as `func5` (Quickstart step 1). v5 auto-starts Azurite and ships extension bundles as a workload, so this template runs with two terminals instead of three. Already on v4? Keep it; the install below leaves `func` alone and adds `func5` alongside. See [Troubleshooting: still using v4](docs/troubleshooting.md#still-using-v4) for the fallback. -- [Azure Developer CLI (`azd`)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/). -- An Azure subscription. `azd provision` (Quickstart step 2) creates the Microsoft Foundry model deployment the agents need - required even for the offline path. -- For real M365 (or `azd up`): permission to authorize Microsoft 365 connectors, plus the `connector-namespace` CLI extension: - -**macOS / Linux:** -```bash -curl -fsSL https://aka.ms/connector-namespace-cli-install | sh -``` - -**Windows:** -```powershell -powershell -Command "Invoke-WebRequest -Uri 'https://aka.ms/connector-namespace-cli-install' -OutFile 'install.sh'; wsl bash install.sh" -``` +- [Python 3.13+](https://docs.astral.sh/uv/) via uv: `uv python install 3.13`. +- [Azure Developer CLI (`azd`)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd). +- [Azure Functions Core Tools v5 (preview)](https://github.com/Azure/azure-functions-core-tools/releases). This template calls the binary `func5` so v5 sits alongside any existing v4 `func`. Already on v4? See [Troubleshooting: still using v4](docs/troubleshooting.md#still-using-v4). +- [Azure CLI `connector-namespace` extension](https://github.com/Azure/Connectors/tree/main/public-preview/connector-namespace-cli) — needed for `azd up` and real M365 connectors. +- An Azure subscription. `azd provision` (Quickstart step 2) creates the Microsoft Foundry model deployment the agents need, required even for the offline path. ## Quickstart @@ -40,32 +30,13 @@ Five steps: install, get resources, run locally, try it, deploy. ### 1. Install the tools -The Azure Functions CLI v5 is in preview. Install the latest preview build from the [Azure Functions Core Tools releases page](https://github.com/Azure/azure-functions-core-tools/releases) (look for `func-osx-arm64.tar.gz` on Apple Silicon, `func-osx-x64.tar.gz` on Intel, `func-linux-x64.tar.gz` / `func-linux-arm64.tar.gz` on Linux, or `func-win-x64.zip` / `func-win-arm64.zip` on Windows). Extract it into its own directory and put a `func5` shim on your `PATH` so v5 sits alongside any existing v4 `func` install. +Follow the prereq links above and make sure the v5 binary is on your `PATH` as `func5`. Then run this once per machine to pull in the Python worker and extension bundles workload (the fix for [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309)): -**macOS / Linux:** ```bash -brew install azure-dev -curl -LsSf https://astral.sh/uv/install.sh | sh - -# Install Functions CLI v5 as func5 (leaves any existing func/v4 alone) -FUNC5_DIR="$HOME/.azure-functions/v5" -mkdir -p "$FUNC5_DIR" && cd "$FUNC5_DIR" -# Swap in the asset for your OS/arch and the latest preview tag from the releases page above -curl -L -o func5.tar.gz \ - https://github.com/Azure/azure-functions-core-tools/releases/download/v5.0.0-preview.2/func-osx-arm64.tar.gz -tar -xzf func5.tar.gz && rm func5.tar.gz -mkdir -p "$HOME/.local/bin" && ln -sf "$FUNC5_DIR/func" "$HOME/.local/bin/func5" -# Add ~/.local/bin to PATH if it isn't already, then in a fresh shell: -func5 setup --features python -``` - -**Windows:** -Install [azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) and [uv](https://docs.astral.sh/uv/getting-started/installation/), then download the v5 preview `func-win-x64.zip` (or `func-win-arm64.zip`) from the releases page, extract it to a folder like `C:\func5`, rename `func.exe` to `func5.exe` so it sits alongside any existing v4 `func.exe`, add `C:\func5` to your `PATH`, and run: -```powershell func5 setup --features python ``` -`func5 setup --features python` is a one-shot install of the v5 host, Python worker, templates, and the extension bundles workload (the fix for [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309)). You only need to run it once per machine. +That installs the v5 host, Python worker, templates, and the extension bundles workload that ships the M365 connectors in one shot. ### 2. Get the resources you need From 003a22a1767226381bc94803b7653b58b274e5c3 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 17:49:32 -0700 Subject: [PATCH 4/6] Drop #5309 framing: bundles workload is included in func5 setup --features python by default Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index b64ce5e..d94fd67 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,12 @@ Five steps: install, get resources, run locally, try it, deploy. ### 1. Install the tools -Follow the prereq links above and make sure the v5 binary is on your `PATH` as `func5`. Then run this once per machine to pull in the Python worker and extension bundles workload (the fix for [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309)): +Follow the prereq links above and make sure the v5 binary is on your `PATH` as `func5`. Then run this once per machine to install the Python worker, templates, and extension bundles (which ships the M365 connectors): ```bash func5 setup --features python ``` -That installs the v5 host, Python worker, templates, and the extension bundles workload that ships the M365 connectors in one shot. - ### 2. Get the resources you need `azd provision` creates the Foundry model deployment the agents require (needed even offline); `hydrate` copies the settings into `local.settings.json`. No API keys - managed identity throughout. From 9f6b78a3bd52e9142222c0b88eb067696aa17f7d Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 18:35:11 -0700 Subject: [PATCH 5/6] Switch local-dev command to 'func5 run' (v5 picks up uv venv automatically); add Azurite to prereqs; link uv (not Python) in prereq bullet - func5 run replaces 'uv run func5 start' everywhere - v5 detects pyproject.toml and uses the uv-managed venv without the explicit 'uv run' prefix. - Azurite added as an explicit prereq with install link; func5 launches it on demand but does not bundle it. - Prereq bullet now links to uv (the thing you install) instead of Python 3.13 (the thing uv installs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 7 ++++--- chat.py | 10 +++++----- docs/configuration.md | 2 +- docs/troubleshooting.md | 10 +++++----- function_app.py | 2 +- infra/scripts/authorize-connectors.ps1 | 2 +- infra/scripts/authorize-connectors.sh | 2 +- infra/scripts/hydrate-local-settings.ps1 | 2 +- infra/scripts/hydrate-local-settings.sh | 2 +- 9 files changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d94fd67..f29868e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ Run it locally in minutes against sample data, point it at your real inbox, then ## Prerequisites -- [Python 3.13+](https://docs.astral.sh/uv/) via uv: `uv python install 3.13`. +- [uv](https://docs.astral.sh/uv/), then `uv python install 3.13`. +- [Azurite](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=npm-package) for local storage emulation (e.g. `npm install -g azurite`). `func5` launches it on demand if it's on your `PATH`. - [Azure Developer CLI (`azd`)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd). - [Azure Functions Core Tools v5 (preview)](https://github.com/Azure/azure-functions-core-tools/releases). This template calls the binary `func5` so v5 sits alongside any existing v4 `func`. Already on v4? See [Troubleshooting: still using v4](docs/troubleshooting.md#still-using-v4). - [Azure CLI `connector-namespace` extension](https://github.com/Azure/Connectors/tree/main/public-preview/connector-namespace-cli) — needed for `azd up` and real M365 connectors. @@ -57,13 +58,13 @@ azd provision ### 3. Run the local client ```bash -uv run func5 start # terminal A (v5 auto-starts Azurite) +func5 run # terminal A (v5 auto-starts Azurite) uv run python chat.py # terminal B ``` (These commands work identically on macOS, Linux, and Windows.) -> 🪟 **Windows local dev:** If `uv run func5 start` fails with `ModuleNotFoundError: No module named 'azure_functions_agents'`, the Microsoft Store python.exe alias on your `PATH` is shadowing the venv Python. See [Troubleshooting: Windows local dev](docs/troubleshooting.md#windows-local-dev) for the fix - you'll need to remove the Store Python and disable App execution aliases in Windows Settings. +> 🪟 **Windows local dev:** If `func5 run` fails with `ModuleNotFoundError: No module named 'azure_functions_agents'`, the Microsoft Store python.exe alias on your `PATH` is shadowing the venv Python. See [Troubleshooting: Windows local dev](docs/troubleshooting.md#windows-local-dev) for the fix - you'll need to remove the Store Python and disable App execution aliases in Windows Settings. ### 4. Try it (offline, safe) diff --git a/chat.py b/chat.py index a080290..0a4f95a 100644 --- a/chat.py +++ b/chat.py @@ -154,7 +154,7 @@ def _print_host_unreachable_hint() -> None: return print(" The local Functions host is not running. Either:") print(" A) Run it locally (2 terminals; v5 auto-starts Azurite):") - print(" terminal A: uv run func5 start") + print(" terminal A: func5 run") print(" terminal B: uv run python chat.py") print(" B) Or call the deployed cloud Function App from this shell:") print(" source ./infra/scripts/use-cloud-host.sh") @@ -621,7 +621,7 @@ def _render_result(agent_name: str, result: dict, elapsed: float) -> None: print(" - Teams post with empty TEAMS_TEAM_ID / TEAMS_CHANNEL_ID") print(" - 3 consecutive failures trip the runtime's circuit breaker, which") print(" stops further tool calls but still returns a (partial) summary.") - print(" Run `uv run func5 start --verbose` to see the exact connector error.") + print(" Run `func5 run --verbose` to see the exact connector error.") live_blocked = mode != "dry_run" and re.search( r"could not read|forbidden|unauthorized|not authoriz", response_text, re.IGNORECASE @@ -661,7 +661,7 @@ def trigger_agent(agent_name: str, mode_icon: str, mode_label: str = "") -> None print(" Check that:") print(" - the .agent.md frontmatter has builtin_endpoints.chat_api: true") print(" - host.json extensions.http.routePrefix matches (this client reads it)") - print(" - you restarted `uv run func5 start` after editing either file") + print(" - you restarted `func5 run` after editing either file") if details: print(f" {details}") print() @@ -726,7 +726,7 @@ def mark(key: str) -> str: print(" Next step to LIVE: `azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com`,") print(" then `./infra/scripts/hydrate-local-settings.sh`. If you have not already") print(" authorized the Outlook connection, also run (one time)") - print(" `./infra/scripts/authorize-connectors.sh`, then restart `uv run func5 start`.") + print(" `./infra/scripts/authorize-connectors.sh`, then restart `func5 run`.") elif not _teams_alerts_enabled(): print(" Outlook is LIVE. Set TEAMS_TEAM_ID / TEAMS_CHANNEL_ID to enable Teams alerts") print(" (inbox escalations and the daily briefing's urgent post).") @@ -920,7 +920,7 @@ def chat_with_inbox() -> None: if exc.code == 404: print(f" No agent responded at {chat_url(CHAT_AGENT[0])}") print(" Check inbox-chat.agent.md has builtin_endpoints.chat_api: true and") - print(" that you restarted `uv run func5 start` after adding it.") + print(" that you restarted `func5 run` after adding it.") if details: print(f" {details}") print() diff --git a/docs/configuration.md b/docs/configuration.md index 5505ab1..2347a31 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ Run the agents against your real Outlook + Teams while still developing locally: azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com ./infra/scripts/hydrate-local-settings.sh ./infra/scripts/authorize-connectors.sh -# Ctrl-C the func host, then `uv run func5 start` again +# Ctrl-C the func host, then `func5 run` again ``` `authorize-connectors.sh` is **required** the first time you go live: it runs a one-time OAuth consent so the connector namespace can read your mailbox and send on your behalf. It opens a browser tab per connection and waits until the connection reports `Connected`. Setting the env vars alone is not enough. Until consent completes, LIVE runs fail with `could not read inbox` (the agent stops and sends nothing). Re-running the script is safe; already-authorized connections are skipped. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 66a95ce..4fbf026 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -5,7 +5,7 @@ ## Local setup - **`Port 7071 is unavailable`**. Another `func` is still running. `lsof -nP -iTCP:7071 -sTCP:LISTEN` to find the PID, then `kill `. -- **`ModuleNotFoundError: agent_functions`**. The Functions host picked a Python worker that can't see the venv. Always start with `uv run func5 start`, not bare `func5 start`. `uv run` prepends `.venv/bin` so the 3.13 worker is selected. +- **`ModuleNotFoundError: agent_functions`**. The Functions host picked a Python worker that can't see the venv. `func5 run` (not bare `func5 start`) detects the uv-managed venv via `pyproject.toml` and selects the 3.13 worker. If you're on a shell where `func5` can't find the venv, `uv run func5 run` is the explicit fallback. - **Worker exits with SIGTERM 143 on startup**. The Python worker on your machine is older than 3.13. Re-run `func5 setup --features python` to pick up the current preview worker. - **`No installed bundle workload satisfies Microsoft.Azure.Functions.ExtensionBundle.Preview`**. The extension bundles workload is missing from your v5 install. Run `func5 workload install azure.functions.cli.workloads.extensionbundles` (or `func5 setup --features python`, which installs it as part of the bundle). This was [Azure/azure-functions-core-tools#5309](https://github.com/Azure/azure-functions-core-tools/issues/5309) on early v5 previews and is fixed in `bundles@4.42.0-preview.2` and later. - **Live mode: `403 Forbidden` from MCP**. The connector connection isn't authorized for the signed-in identity. Re-run `./infra/scripts/authorize-connectors.sh` and complete the browser consent for both Outlook and Teams. @@ -17,14 +17,14 @@ If you have an existing Azure Functions Core Tools v4 install (`brew install azure-functions-core-tools@4` on macOS, or the v4 installer on Windows / Linux) and don't want to add v5 yet, you can still run this template: - Substitute `func` for `func5` everywhere in this repo (`uv run func start`, etc.). -- Start Azurite yourself in a separate terminal before `func start`: `azurite --silent --skipApiVersionCheck --location .azurite`. v4 does not auto-manage it. +- Start Azurite yourself in a separate terminal before `func start`: `azurite --silent --skipApiVersionCheck --location .azurite`. v4 does not auto-launch it (v5 does, if `azurite` is on your `PATH`). - Make sure your Core Tools is ≥ 4.12.0 so the Python 3.13 worker ships. The v5 path is recommended because it removes the Azurite terminal and ships the extension bundles workload that this template depends on. Once v5 reaches GA, this fallback section goes away. ## Windows local dev -**Symptom:** `uv run func5 start` or `func5 start` fails with: +**Symptom:** `func5 run` fails with: ``` ModuleNotFoundError: No module named 'azure_functions_agents' ``` @@ -63,7 +63,7 @@ ModuleNotFoundError: No module named 'pydantic_core._pydantic_core' 4. **Retry:** ```powershell - uv run func5 start + func5 run ``` **Note on `hydrate-local-settings.ps1`:** The provisioning script can also set `languageWorkers__python__defaultExecutablePath` to point at your venv Python, which helps — but this mitigation is **not sufficient alone**. The OS-level `PATH` search still happens first, so you must still remove the Store Python and disable aliases. Both steps together ensure the 3.13 worker loads correctly. @@ -76,7 +76,7 @@ ModuleNotFoundError: No module named 'pydantic_core._pydantic_core' | --- | --- | | Connector authorization fails | Reopen the Connector Namespace portal URL from deployment outputs, sign in with the mailbox/channel owner, and reauthorize Outlook and Teams. | | MCP endpoint missing | Run `azd env get-values` and confirm `OUTLOOK_MCP_ENDPOINT` and `TEAMS_MCP_ENDPOINT` are populated. If blank, rerun `azd up` and check Connector Namespace deployment logs. | -| Timer is not firing | Confirm the Functions host shows the timer trigger loaded at startup. The v5 CLI starts Azurite automatically; pass `--no-azurite` only if you intentionally point `AzureWebJobsStorage` elsewhere. | +| Timer is not firing | Confirm the Functions host shows the timer trigger loaded at startup. `func5 run` launches Azurite automatically if it's on your `PATH`; pass `--no-azurite` only if you intentionally point `AzureWebJobsStorage` elsewhere. | | Local run cannot reach Azure | Leave the MCP endpoint variables blank and use `chat.py`; every agent runs DRY RUN and prints its deliverable as text. Option 4 shows what's missing to go live. | | Manual trigger returns 404 | Confirm the Functions host is running and agent function names are `inbox-triage`, `daily-briefing`, and `weekly-rule-suggestions`. | | Deploy fails: `azure-functions==2.1.0 ... No matching distribution found` | The Flex remote build is using Python 3.11.8 instead of 3.13. Use the pre-built deploy workaround in [deploy-python-313.md](deploy-python-313.md). Tracking: [Azure/azure-dev#8538](https://github.com/Azure/azure-dev/issues/8538). | diff --git a/function_app.py b/function_app.py index 63732e2..58a0188 100644 --- a/function_app.py +++ b/function_app.py @@ -73,7 +73,7 @@ def _resolve(key: str) -> str: banner.append("Fix: edit local.settings.json, or run:") for key, _ in issues: banner.append(f" azd env set {key} ") - banner.append("Then re-run `./infra/scripts/hydrate-local-settings.sh` and restart `uv run func5 start`.") + banner.append("Then re-run `./infra/scripts/hydrate-local-settings.sh` and restart `func5 run`.") banner.append("=" * 78) print("\n".join(banner), file=sys.stderr, flush=True) diff --git a/infra/scripts/authorize-connectors.ps1 b/infra/scripts/authorize-connectors.ps1 index f35e1f0..2ee4836 100644 --- a/infra/scripts/authorize-connectors.ps1 +++ b/infra/scripts/authorize-connectors.ps1 @@ -4,7 +4,7 @@ # # Safe to re-run: already-authorized connections are skipped. # Works without a deployed function app. Runs against the connector namespace -# directly so local `uv run func5 start` can call real MCP tools. +# directly so local `func5 run` can call real MCP tools. # # Prereq: the `connector-namespace` Azure CLI extension. Install once with: # az extension add --source Date: Tue, 16 Jun 2026 18:36:59 -0700 Subject: [PATCH 6/6] docs: inline az lookup for TEAMS_MENTION_* env vars Add TEAMS_MENTION_USER_ID and TEAMS_MENTION_NAME to the README go-live block with inlined z ad signed-in-user show subshells so users don't have to copy ids manually. Mirror the same snippet in docs/configuration.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 12 ++++++++---- docs/configuration.md | 7 +++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f29868e..eca36cd 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,16 @@ Want it to act on your real inbox while still local? See [Go live with real M365 Set who real mail and Teams posts go to, **before** you deploy. Without these, the deployed agents stay in DRY RUN (the chat client's doctor banner will tell you). ```bash -azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com # required for LIVE mail -azd env set TEAMS_TEAM_ID # optional, enables Teams alerts -azd env set TEAMS_CHANNEL_ID # optional, enables Teams alerts +azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com # required for LIVE mail +azd env set TEAMS_TEAM_ID # optional, enables Teams alerts +azd env set TEAMS_CHANNEL_ID # optional, enables Teams alerts +azd env set TEAMS_MENTION_USER_ID "$(az ad signed-in-user show --query id -o tsv)" # @mentions you on urgent alerts +azd env set TEAMS_MENTION_NAME "$(az ad signed-in-user show --query displayName -o tsv)" # display name for the @mention ``` -Get the Teams ids by opening the target channel in Teams → ⋯ → **Get link to channel** (the URL contains both ids), or via [docs/configuration.md](docs/configuration.md). +Get the Teams ids by opening the target channel in Teams → ⋯ → **Get link to channel** (the URL contains both `groupId=` → `TEAMS_TEAM_ID` and `19:...@thread.tacv2` → `TEAMS_CHANNEL_ID`). + +Or run `./infra/scripts/discover-teams-ids.ps1` (or `.sh`) to print all four `azd env set` lines pre-filled. More in [docs/configuration.md](docs/configuration.md). > Already deployed with placeholders? Set them now and re-run `azd up` (or just `azd provision`) to push the new values to the Function App. diff --git a/docs/configuration.md b/docs/configuration.md index 2347a31..0b52c99 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,6 +15,13 @@ azd env set MAILBOX_OWNER_EMAIL you@your-tenant.com # Ctrl-C the func host, then `func5 run` again ``` +Want Teams alerts to @mention you (not just post into the channel)? Inline `az` lookups so you don't have to copy ids: + +```bash +azd env set TEAMS_MENTION_USER_ID "$(az ad signed-in-user show --query id -o tsv)" +azd env set TEAMS_MENTION_NAME "$(az ad signed-in-user show --query displayName -o tsv)" +``` + `authorize-connectors.sh` is **required** the first time you go live: it runs a one-time OAuth consent so the connector namespace can read your mailbox and send on your behalf. It opens a browser tab per connection and waits until the connection reports `Connected`. Setting the env vars alone is not enough. Until consent completes, LIVE runs fail with `could not read inbox` (the agent stops and sends nothing). Re-running the script is safe; already-authorized connections are skipped. `chat.py` now shows 🟢 Live. Pick 1, 2, or 3, and the agents read your real inbox and send real mail / Teams posts. `MAILBOX_OWNER_EMAIL` is a safety guardrail: outbound digests go only to that address. Start with your own.