From c6093bb357daca30e31e4f98a786d4d204b73e39 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 13:56:34 -0700 Subject: [PATCH 1/7] Fix hydrate-local-settings next-step hint to match README workflow The script printed 'Run func start to start the host', which is incomplete and skips the actual local-run workflow documented in the README: azurite --silent --skipApiVersionCheck --location .azurite # terminal A uv run func start # terminal B uv run python chat.py # terminal C Updated both the bash and PowerShell variants to print the three-terminal block so users land on the working path without re-reading the README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/scripts/hydrate-local-settings.ps1 | 6 +++++- infra/scripts/hydrate-local-settings.sh | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/infra/scripts/hydrate-local-settings.ps1 b/infra/scripts/hydrate-local-settings.ps1 index b5a5c11..a03d5fb 100644 --- a/infra/scripts/hydrate-local-settings.ps1 +++ b/infra/scripts/hydrate-local-settings.ps1 @@ -78,4 +78,8 @@ $settings = [ordered]@{ $settings | ConvertTo-Json -Depth 5 | Set-Content -Path local.settings.json -Encoding UTF8 Write-Host "Wrote local.settings.json -- provider=foundry, model=$($envVars['FOUNDRY_MODEL'])" -Write-Host "Run 'func start' to start the host." +Write-Host "" +Write-Host "Next: run these in three separate terminals from the project root:" +Write-Host " azurite --silent --skipApiVersionCheck --location .azurite # terminal A" +Write-Host " uv run func start # terminal B" +Write-Host " uv run python chat.py # terminal C" diff --git a/infra/scripts/hydrate-local-settings.sh b/infra/scripts/hydrate-local-settings.sh index 13a9326..0660e7c 100755 --- a/infra/scripts/hydrate-local-settings.sh +++ b/infra/scripts/hydrate-local-settings.sh @@ -42,4 +42,8 @@ EOF chmod 600 local.settings.json echo "Wrote local.settings.json โ€” provider=foundry, model=${FOUNDRY_MODEL}" -echo "Run 'func start' to start the host." +echo "" +echo "Next: run these in three separate terminals from the project root:" +echo " azurite --silent --skipApiVersionCheck --location .azurite # terminal A" +echo " uv run func start # terminal B" +echo " uv run python chat.py # terminal C" From a0eb5245c5d816904e1dbec6c2524a906c54a8a2 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 14:03:14 -0700 Subject: [PATCH 2/7] README: move 'Make it yours' before 'Clean up' Customize-before-teardown reads more naturally. Promotes 'Make it yours' into step 6 of the Quickstart, with 'Clean up' as the closing step 7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0f797f7..86b94f6 100644 --- a/README.md +++ b/README.md @@ -98,15 +98,7 @@ azd up Now `inbox-triage` fires automatically on every new email โ€” no client, no waiting. Send yourself a message, then watch your Teams channel (VIP / incident) or your inbox (replies). Tail the live trace with `azd monitor --logs`. -### Clean up - -```bash -azd down --purge -``` - -> Hitting an error? See [docs/troubleshooting.md](docs/troubleshooting.md). - -## Make it yours +### 6. Make it yours - โœ๏ธ Edit `skills/vip-rules.md` to set your VIPs, what to skip, and what escalates to Teams. - ๐Ÿ” The `weekly-rule-suggestions` agent proposes tuning that you approve by hand. @@ -114,6 +106,14 @@ azd down --purge โ†’ Full guide: [docs/customize.md](docs/customize.md). +### 7. Clean up + +```bash +azd down --purge +``` + +> Hitting an error? See [docs/troubleshooting.md](docs/troubleshooting.md). + ## How it works (the short version) This is an Azure Functions app on the serverless agents runtime. Each agent is a markdown file (`*.agent.md`) that reasons over your rules in `skills/*.md`; a small `tools/match_rule.py` adds deterministic classification. Microsoft 365 actions go through Entra-authorized MCP connectors โ€” no app secrets, managed identity end to end. From 83aa7c1ed779f282fbe7886070a75f3002fd3e49 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 14:19:34 -0700 Subject: [PATCH 3/7] README: add 'Go live' step before deploy Sets MAILBOX_OWNER_EMAIL (required for LIVE mail) and the optional TEAMS_TEAM_ID / TEAMS_CHANNEL_ID before `azd up`, so the deployed agents are LIVE on first deploy instead of starting in DRY RUN with placeholder values. Renumbers the trailing Quickstart steps. Stacks on top of #13 (Make it yours before Clean up). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 86b94f6..3bb64b4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,21 @@ Pick **1**, **2**, or **3**. The client shows a ๐ŸŸก Offline banner and runs eve Want it to act on your real inbox while still local? See [Go live with real M365](docs/configuration.md#go-live-with-real-m365). -### 5. Deploy to Azure, then try it again +### 5. Go live (set your recipient and Teams target) + +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 +``` + +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). + +> 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. + +### 6. Deploy to Azure, then try it again ```bash azd up @@ -98,7 +112,7 @@ azd up Now `inbox-triage` fires automatically on every new email โ€” no client, no waiting. Send yourself a message, then watch your Teams channel (VIP / incident) or your inbox (replies). Tail the live trace with `azd monitor --logs`. -### 6. Make it yours +### 7. Make it yours - โœ๏ธ Edit `skills/vip-rules.md` to set your VIPs, what to skip, and what escalates to Teams. - ๐Ÿ” The `weekly-rule-suggestions` agent proposes tuning that you approve by hand. @@ -106,7 +120,7 @@ Now `inbox-triage` fires automatically on every new email โ€” no client, no wait โ†’ Full guide: [docs/customize.md](docs/customize.md). -### 7. Clean up +### 8. Clean up ```bash azd down --purge From ee2e62eb36e833e235a2b29a01fe6ee35ba72c5e Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Tue, 16 Jun 2026 14:44:29 -0700 Subject: [PATCH 4/7] azure.yaml: hydrate local.settings.json in postdeploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chain hydrate-local-settings before configure-trigger so 'azd up' / 'azd provision' keeps both the deployed Function App app settings AND local.settings.json in sync from the same source of truth (azd env). Today, 'azd env set MAILBOX_OWNER_EMAIL ...' followed by 'azd provision' pushes the new value to the deployed app but leaves local.settings.json stale โ€” the local chat client then reports 'Partial: placeholder' even though the deployed app is correctly LIVE. Stacks on #15. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure.yaml b/azure.yaml index 09429a1..e427c5c 100644 --- a/azure.yaml +++ b/azure.yaml @@ -33,11 +33,11 @@ hooks: postdeploy: posix: shell: sh - run: ./infra/scripts/configure-trigger.sh + run: ./infra/scripts/hydrate-local-settings.sh && ./infra/scripts/configure-trigger.sh interactive: true continueOnError: false windows: shell: pwsh - run: ./infra/scripts/configure-trigger.ps1 + run: ./infra/scripts/hydrate-local-settings.ps1; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; ./infra/scripts/configure-trigger.ps1 interactive: true continueOnError: false From a98290911c8c5e1257c42a2d4bc25cf3ca38d6cc Mon Sep 17 00:00:00 2001 From: Paul Yuk Date: Tue, 16 Jun 2026 14:59:32 -0700 Subject: [PATCH 5/7] chat.py: probe Functions host in doctor + use-cloud-host helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'Show config readiness' doctor only inspected env vars; if the local Functions host wasn't running, you'd only learn that when an agent call failed with a generic 'Connection refused'. And there was no documented way to point chat.py at the deployed cloud Function App. This adds: * `infra/scripts/use-cloud-host.{sh,ps1}` โ€” sourceable helpers that export AGENT_URL + FUNCTION_KEY from `azd env` + `az functionapp keys list`, so the chat client hits the deployed Function App in Azure without running azurite + func start locally. * Doctor (option 4) shows a new 'Functions host' row up top with ๐ŸŸข/๐Ÿ”ด + which host (local vs cloud) is being targeted and whether it's reachable. * When a chat call fails with a connection error, the hint now spells out BOTH paths: the 3-terminal local setup AND the one-liner to switch to the deployed cloud host. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- chat.py | 50 ++++++++++++++++++++++++++++++-- infra/scripts/use-cloud-host.ps1 | 24 +++++++++++++++ infra/scripts/use-cloud-host.sh | 26 +++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 infra/scripts/use-cloud-host.ps1 create mode 100755 infra/scripts/use-cloud-host.sh diff --git a/chat.py b/chat.py index 0b442aa..0563720 100644 --- a/chat.py +++ b/chat.py @@ -124,6 +124,43 @@ def chat_url(agent_name: str) -> str: return url +def _host_kind() -> str: + """'cloud' if BASE_URL points to *.azurewebsites.net, else 'local'.""" + return "cloud" if "azurewebsites.net" in BASE_URL else "local" + + +def _host_reachable(timeout: float = 2.0) -> bool: + """True if the Functions host at BASE_URL responds at all (any HTTP code). + + Connection refused / DNS failure / TLS error => host is not reachable. + """ + probe = f"{BASE_URL}/admin/host/status" + try: + urllib.request.urlopen(probe, timeout=timeout) + return True + except urllib.error.HTTPError: + return True + except Exception: + return False + + +def _print_host_unreachable_hint() -> None: + """Shared 'host is down' guidance โ€” pointed at local and cloud paths.""" + if _host_kind() == "cloud": + print(" AGENT_URL points to the cloud Function App but it did not respond.") + print(" Re-run: `source ./infra/scripts/use-cloud-host.sh` (refreshes the key),") + 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(" 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") + + _TEAMS_TRIGGERING_RX = re.compile(r"urgent|p1\b|incident|escalat|outage", re.IGNORECASE) _SKIP_RX = re.compile(r"^\s*fyi\b|newsletter", re.IGNORECASE) @@ -625,7 +662,8 @@ def trigger_agent(agent_name: str, mode_icon: str, mode_label: str = "") -> None return except Exception as exc: print(f"\nError calling {agent_name} /chat: {exc}") - print("Is the Functions host running with `uv run func start`?\n") + _print_host_unreachable_hint() + print() return elapsed = time.monotonic() - start @@ -645,9 +683,16 @@ def mark(key: str) -> str: print("\nConfig readiness") print("================") + kind = _host_kind() + up = _host_reachable() + host_label = f"{'๐ŸŸข' if up else '๐Ÿ”ด'} {kind} ({BASE_URL}) {'reachable' if up else 'NOT reachable'}" + print(f" {'Functions host':<22} {host_label}") for key in ("OUTLOOK_MCP_ENDPOINT", "MAILBOX_OWNER_EMAIL", "TEAMS_TEAM_ID", "TEAMS_CHANNEL_ID"): print(f" {key:<22} {mark(key)}") print() + if not up: + _print_host_unreachable_hint() + print() print(f" {'Agent':<26} {'Mode':<9} {'Sends email':<13} Posts Teams") print(f" {'-' * 26} {'-' * 9} {'-' * 13} {'-' * 11}") for label, name in ( @@ -878,7 +923,8 @@ def chat_with_inbox() -> None: continue except Exception as exc: print(f"\n Error: {exc}") - print(" Is the Functions host running with `uv run func start`?\n") + _print_host_unreachable_hint() + print() continue response_text = (result.get("response") or "").strip() diff --git a/infra/scripts/use-cloud-host.ps1 b/infra/scripts/use-cloud-host.ps1 new file mode 100644 index 0000000..bf0494d --- /dev/null +++ b/infra/scripts/use-cloud-host.ps1 @@ -0,0 +1,24 @@ +# Dot-source (do not run) this file: +# . ./infra/scripts/use-cloud-host.ps1 +# It sets $env:AGENT_URL + $env:FUNCTION_KEY so `uv run python chat.py` calls +# the deployed Function App in Azure instead of the local Functions host +# (skips needing `azurite` + `uv run func start` in two extra terminals). + +$ErrorActionPreference = "Stop" + +$uri = azd env get-value SERVICE_API_URI 2>$null +if (-not $uri) { Write-Error "no azd env โ€” run 'azd up' first"; return } +$rg = azd env get-value RESOURCE_GROUP 2>$null +if (-not $rg) { Write-Error "RESOURCE_GROUP not in azd env"; return } +$app = azd env get-value AZURE_FUNCTION_APP_NAME 2>$null +if (-not $app) { Write-Error "AZURE_FUNCTION_APP_NAME not in azd env"; return } + +$key = az functionapp keys list -g $rg -n $app --query functionKeys.default -o tsv 2>$null +if (-not $key) { Write-Error "could not fetch function key โ€” try 'az login'"; return } + +$env:AGENT_URL = $uri +$env:FUNCTION_KEY = $key +Write-Host "AGENT_URL=$uri" +$tail = $key.Substring([Math]::Max(0, $key.Length - 4)) +Write-Host "FUNCTION_KEY=***$tail" +Write-Host "chat.py will now call the deployed Function App ($app)." diff --git a/infra/scripts/use-cloud-host.sh b/infra/scripts/use-cloud-host.sh new file mode 100755 index 0000000..da26ff0 --- /dev/null +++ b/infra/scripts/use-cloud-host.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Source (do not execute) this file: +# source ./infra/scripts/use-cloud-host.sh +# It exports AGENT_URL + FUNCTION_KEY so `uv run python chat.py` calls the +# deployed Function App in Azure instead of the local Functions host +# (skips needing `azurite` + `uv run func start` in two extra terminals). + +_die() { echo "use-cloud-host: $*" >&2; return 1 2>/dev/null || exit 1; } + +command -v azd >/dev/null || _die "azd not on PATH" || return 1 +command -v az >/dev/null || _die "az not on PATH" || return 1 + +_uri=$(azd env get-value SERVICE_API_URI 2>/dev/null) || _die "no azd env โ€” run 'azd up' first" || return 1 +_rg=$(azd env get-value RESOURCE_GROUP 2>/dev/null) || _die "RESOURCE_GROUP not in azd env" || return 1 +_app=$(azd env get-value AZURE_FUNCTION_APP_NAME 2>/dev/null) || _die "AZURE_FUNCTION_APP_NAME not in azd env" || return 1 + +_key=$(az functionapp keys list -g "$_rg" -n "$_app" --query functionKeys.default -o tsv 2>/dev/null) \ + || _die "could not fetch function key โ€” try 'az login'" || return 1 +[ -n "$_key" ] || _die "function key empty โ€” host may still be starting" || return 1 + +export AGENT_URL="$_uri" +export FUNCTION_KEY="$_key" +echo "AGENT_URL=$AGENT_URL" +echo "FUNCTION_KEY=***${_key: -4}" +echo "chat.py will now call the deployed Function App ($_app)." +unset _uri _rg _app _key From 9d18108eeb3b1bd6695a33c75c73057fe12e3b8c Mon Sep 17 00:00:00 2001 From: Paul Yuk Date: Tue, 16 Jun 2026 15:08:28 -0700 Subject: [PATCH 6/7] README: lead with Daily Briefing in 'What it does for you' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Daily Briefing is the most concrete, demo-able value of the agent โ€” it's what people see on day one once they've deployed. Lead with it and use the full noun phrase 'Daily Briefing' (matches the menu label in chat.py and the agent name) instead of the verb-form 'Brief'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bb64b4..10b3e1b 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ Run it locally in minutes against sample data, point it at your real inbox, then ## What it does for you +- ๐Ÿ“‹ **Daily Briefing** โ€” a daily summary of what matters lands in your inbox. - ๐Ÿšจ **Escalate** โ€” VIP / urgent mail is posted to your Teams channel, with an @mention. - โœ‰๏ธ **Reply** โ€” action-required mail gets a grounded draft reply. -- ๐Ÿ“‹ **Brief** โ€” a daily summary of what matters lands in your inbox. - ๐Ÿ’ฌ **Chat** โ€” ask read-only questions about your recent mail. โ†’ Full walkthroughs in [docs/use-cases.md](docs/use-cases.md). From 099a64c1c51f05a5802b2bb7e91c3d5d0052e881 Mon Sep 17 00:00:00 2001 From: Paul Yuk Date: Tue, 16 Jun 2026 15:09:13 -0700 Subject: [PATCH 7/7] README: drop em dashes (use colons in bullet defs, hyphens elsewhere) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 10b3e1b..1e00cbc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # M365 Inbox Agent for Azure Functions (Python) [![Python](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/downloads/) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?repo=Azure-Samples%2Fm365-inbox-serverless-agent-python) -An AI agent that triages your Microsoft 365 inbox: it escalates urgent mail to Teams, drafts replies, and emails you a daily briefing โ€” all following rules you write in plain markdown. +An AI agent that triages your Microsoft 365 inbox: it escalates urgent mail to Teams, drafts replies, and emails you a daily briefing - all following rules you write in plain markdown. **Powered by Azure Functions and Microsoft 365 connectors. You bring the markdown logic and a little Python for your rules.** @@ -9,10 +9,10 @@ Run it locally in minutes against sample data, point it at your real inbox, then ## What it does for you -- ๐Ÿ“‹ **Daily Briefing** โ€” a daily summary of what matters lands in your inbox. -- ๐Ÿšจ **Escalate** โ€” VIP / urgent mail is posted to your Teams channel, with an @mention. -- โœ‰๏ธ **Reply** โ€” action-required mail gets a grounded draft reply. -- ๐Ÿ’ฌ **Chat** โ€” ask read-only questions about your recent mail. +- ๐Ÿ“‹ **Daily Briefing**: a daily summary of what matters lands in your inbox. +- ๐Ÿšจ **Escalate**: VIP / urgent mail is posted to your Teams channel, with an @mention. +- โœ‰๏ธ **Reply**: action-required mail gets a grounded draft reply. +- ๐Ÿ’ฌ **Chat**: ask read-only questions about your recent mail. โ†’ Full walkthroughs in [docs/use-cases.md](docs/use-cases.md). @@ -21,7 +21,7 @@ Run it locally in minutes against sample data, point it at your real inbox, then - 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 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. +- 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:** @@ -54,7 +54,7 @@ Use the [Core Tools v4 install guide](https://learn.microsoft.com/en-us/azure/az ### 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. +`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. **macOS / Linux:** ```bash @@ -68,7 +68,7 @@ azd provision .\infra\scripts\hydrate-local-settings.ps1 ``` -> Yes โ€” even the offline, sample-data run needs this one-time `azd provision`, because the agents call a Foundry model. It provisions the model deployment only; it never touches your inbox. +> Yes - even the offline, sample-data run needs this one-time `azd provision`, because the agents call a Foundry model. It provisions the model deployment only; it never touches your inbox. ### 3. Run the local client @@ -80,7 +80,7 @@ uv run python chat.py # terminal C (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 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. ### 4. Try it (offline, safe) @@ -108,9 +108,9 @@ Get the Teams ids by opening the target channel in Teams โ†’ โ‹ฏ โ†’ **Get link azd up ``` -> **Known issue (point-in-time):** `azd up` may currently fail at the deploy step on Python 3.13 โ€” the Flex remote build uses Python 3.11.8 ([Azure/azure-dev#8538](https://github.com/Azure/azure-dev/issues/8538)). Simple workaround: [docs/deploy-python-313.md](docs/deploy-python-313.md). This note can be removed once the bug is fixed. +> **Known issue (point-in-time):** `azd up` may currently fail at the deploy step on Python 3.13 - the Flex remote build uses Python 3.11.8 ([Azure/azure-dev#8538](https://github.com/Azure/azure-dev/issues/8538)). Simple workaround: [docs/deploy-python-313.md](docs/deploy-python-313.md). This note can be removed once the bug is fixed. -Now `inbox-triage` fires automatically on every new email โ€” no client, no waiting. Send yourself a message, then watch your Teams channel (VIP / incident) or your inbox (replies). Tail the live trace with `azd monitor --logs`. +Now `inbox-triage` fires automatically on every new email - no client, no waiting. Send yourself a message, then watch your Teams channel (VIP / incident) or your inbox (replies). Tail the live trace with `azd monitor --logs`. ### 7. Make it yours @@ -130,7 +130,7 @@ azd down --purge ## How it works (the short version) -This is an Azure Functions app on the serverless agents runtime. Each agent is a markdown file (`*.agent.md`) that reasons over your rules in `skills/*.md`; a small `tools/match_rule.py` adds deterministic classification. Microsoft 365 actions go through Entra-authorized MCP connectors โ€” no app secrets, managed identity end to end. +This is an Azure Functions app on the serverless agents runtime. Each agent is a markdown file (`*.agent.md`) that reasons over your rules in `skills/*.md`; a small `tools/match_rule.py` adds deterministic classification. Microsoft 365 actions go through Entra-authorized MCP connectors - no app secrets, managed identity end to end. โ†’ Deeper dives: [How it works](docs/how-it-works.md) ยท [Configuration & deployment](docs/configuration.md) ยท [Customize](docs/customize.md) ยท [Troubleshooting & reference](docs/troubleshooting.md)