# Quick Start: Hosted Foundry Agent

Opinionated happy-path for first-time users creating their first Foundry hosted agent. Safe defaults, minimal decisions.

## When to Use This Skill

Use this when the request is to create a new Foundry hosted agent end-to-end — scaffold, provision, deploy, and smoke-test. Common overrides (language, region, sample, topic, existing project, existing model) are fine. This skill supports only the `responses` and `invocations` protocols. When working on existing agents, using container deploy, using the `activity` protocol, or handling anything else not covered here, stop and read [create-hosted.md](create-hosted.md).

## Quick Reference

| Property | Standard path | Override |
|----------|---------------|----------|
| Sample | Foundry hosted agent samples for the chosen language (`azd ai agent sample list --language <lang> --output json`) | User may name a different sample |
| Model version | Whatever the sample's manifest declares | If user supplies a model version, edit `azure.yaml`. If user supplies a model deployment without model version, follow [Foundry Model Reference](./references/foundry-model.md) to query model-related data. If provision fails, follow [Foundry Model Reference](./references/foundry-model.md) to query model-related data. |
| Model quota | Skip the quota pre-check | If provision fails, follow [Foundry Model Reference](./references/foundry-model.md) to query model-related data. |
| Stops at | Deployed agent + remote smoke invoke + eval generation submitted | — |

## Workflow

Walk through every step in order.

### Step 1 — Verify the environment

Run the bundled read-only Copilot app entry preflight without asking for approval; it locates the app's Copilot CLI and reports whether the `microsoft-foundry` canvas plugin needs installation:

```bash
./scripts/check-copilot-app-entry.sh     # macOS / Linux
./scripts/check-copilot-app-entry.ps1    # Windows (pwsh)
```

Act on the summary prefixes:

- `[OK]` -- nothing to do.
- `[WARN]` -- non-blocking; continue.
- `[ACTION]` -- try to resolve by using the exact plugin install command emitted by the preflight; ask before installing in interactive mode, and install directly in non-interactive mode.
  - **On successful installation, you MUST print:** "The `microsoft-foundry` canvas extension is installed and will be available in a new session." Then rerun the preflight.
  - If installation is declined or fails, warn and continue; do not retry.

Then run the bundled verification script:

```bash
./scripts/verify-environment.sh     # macOS / Linux
./scripts/verify-environment.ps1    # Windows (pwsh)
```

Act on the summary prefixes:

- `[OK]` -- nothing to do.
- `[WARN]` -- non-blocking; continue.
- `[ACTION]` -- resolve first, then rerun the script. If `az` or `azd` is missing, ask before installing in interactive mode; install directly in non-interactive mode. For how to install `azd`, see <https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd>. In any mode, never run `az login` or `azd auth login`; stop and ask the user to log in manually before any init, provision, or deploy command. Missing `azure.ai.agents` / `azure.ai.projects` extensions may be resolved with `azd extension install <name>`.

### Step 2 — Collect necessary information

Before asking, resolve values from the user's request, the workspace,
`azure.yaml`, the Step 1 verification output, and `azd env get-values`. For each
row, do not ask when its **When to skip** condition is met. Ask for all
remaining applicable values in one `AskUserQuestion` round. Do not ask for
values that are already resolved or irrelevant to the requested change.
Populate each question with the default option below.

| Value | When to skip | Default option | Notes |
|-------|--------------|----------------|-------|
| Project / agent name | The user provided one, or the existing code already defines one. | Project: `ai-project-<random>`; agent: the selected sample's agent name | Generate `<random>` using 6-8 lowercase alphanumeric characters. Pass the agent name to `azd ai agent init` with `--agent-name`; it sets the service key and agent name in `azure.yaml`. For a new Foundry project, set the project name after init with `azd env set AZURE_AI_PROJECT_NAME "<project-name>"` before running `azd provision`. |
| Language | The user provided one, or the existing code already determines it. | Python | Supported languages: Python and .NET. |
| Subscription | The active azd environment already contains the intended `AZURE_SUBSCRIPTION_ID`. | Active Azure subscription: `<subscription-name>` (`<subscription-id>`) | Resolve both values with `az account show --query "{name:name,id:id}" -o json`; the ID must be a subscription GUID. |
| Region | The active azd environment already contains the intended `AZURE_LOCATION`, or this change does not provision regional resources. | `northcentralus` | Azure resource location. |
| Foundry project | The workspace or azd environment is already configured with a Foundry project, or the user provided one. | New Foundry project | Offer a new or existing project. For a new project, do not pass `--project-id`; `azd provision` creates it. For an existing project, use its ARM resource ID with `azd ai agent init --project-id`. |
| Foundry model deployment | The user provided one. | Official sample's model selection | If the user specifies a model deployment, collect its deployment name. |
| Deploy mode | Always — resolve without asking. | `code` | Container deploy is not supported in this quick start. To use container deploy, follow [create-hosted.md](create-hosted.md). |

If the user supplied only a **Foundry project endpoint** (not an ARM ID), resolve the ARM ID before Step 4:

```bash
./scripts/resolve-project-id.sh --endpoint "<foundry-project-endpoint>"     # macOS / Linux
./scripts/resolve-project-id.ps1 -Endpoint "<foundry-project-endpoint>"     # Windows (pwsh)
```

Use the returned `id` value. Never guess or construct the ARM ID from the endpoint.

When creating a new agent in an existing Foundry project, verify that the selected Foundry model deployment exists by running:

```bash
az cognitiveservices account deployment list \
  --resource-group "<rg-name>" \
  --name "<foundry-account-name>" \
  --output table
```

### Step 3 — Pick the sample

```bash
azd ai agent sample list --language <lang> --output json
```

> `--language` here takes the short form (`python`, `dotnetCsharp`) — not the runtime token (`python_3_13` fails with `unknown language`). The runtime tokens are only used in Step 4's `azd ai agent init --runtime ...`.

Capture the `manifestUrl`.

> **Important:** Always select the best-matching sample from `azd ai agent sample list` for the capabilities the user explicitly requested. Use advanced tool samples only when the user explicitly asks for external actions, APIs, tools, connectors, or data lookup. Starting with the right sample helps ensure that the implementation follows the established code patterns and best practices for that type of Foundry hosted agent. If `azd ai agent sample list` does not return a suitable sample, choose one from the official [Foundry samples repository](https://github.com/microsoft-foundry/foundry-samples) and construct the manifest URL from its exact `azure.yaml` path, following the URL format returned by `azd ai agent sample list`.

You should pick only one sample for `azd ai agent init`, but you can browse multiple samples relevant to user's task as code reference.

Step 4 needs `--runtime` and `--entry-point` values. These are CLI args, **not** fields in the manifest — use these standard defaults for the chosen language:

| Language | `--runtime` | `--entry-point` |
|----------|-------------|-----------------|
| Python | `python_3_13` | `main.py` |
| .NET | `dotnet_10` | `MyAgent.dll` |

### Step 4 — Scaffold the agent

Run `azd ai agent init`. `azd ai agent init` is sufficient to create new Foundry projects (or reuse an existing one) and create new Foundry agents. By default, you do not need to run `azd init` unless the user has specific initialization requirements.

Pass `--deploy-mode code` by default to use code deploy.

```bash
azd ai agent init --no-prompt \
  -m "<manifestUrl>" \
  --deploy-mode code \
  --runtime python_3_13 \
  --entry-point main.py \
  --agent-name "<agent-name>"
```

After the `azd ai agent init` completes, go to the project folder and write the subscription and region collected in Step 2 to the active azd environment:

```bash
azd env set AZURE_SUBSCRIPTION_ID "<subscription-id>"
azd env set AZURE_LOCATION "<region>"
```

When creating a new Foundry project, also set its name before provisioning:

```bash
azd env set AZURE_AI_PROJECT_NAME "<project-name>"
```

Values you **must** substitute from Step 3 — do not pass placeholders or guesses:

- `--runtime`: exactly one of `python_3_13`, `python_3_14`, `dotnet_10` (the bare value `python` fails with `--runtime must be one of: python_3_13, python_3_14, dotnet_10`).
- `--entry-point`: the entry-point file the sample declares (e.g. `main.py`, not `app.py` — a wrong value scaffolds correctly but breaks local run and deploy).

If using an existing Foundry project, add `--project-id "<arm-id>"`.

⏳ May take time — init resolves the model catalog server-side. Wait for the prompt to return; do not interrupt.

`init` writes `azure.yaml` (appending the agent service), `src/<agent-name>/.agentignore`, and the sample source files under `src/<agent-name>/`.

> **Important:** Do not chain `azd env set` after `azd ai agent init` on the same command line. The init command may scaffold the project into a subfolder, so run `azd env set` only after initialization completes and after changing to the scaffolded project directory.

### Step 5 — Customize the scaffolded sample (per user's original intent)

The scaffold is a generic working sample. Edit only what the user's original prompt asked for — touch tools, dependencies, or model config only when the user explicitly asked for external actions, APIs, tools, connectors, data lookup, or a specific model.

Typical changes:

- The agent service's `description:` in `azure.yaml` — update it to match the user's intent (this also feeds Step 13 eval generation).
- `src/<agent-name>/<entry-point>` — update the system prompt / instructions to match the user's intent.

Only when the user explicitly asked for it:

- Add or modify tool / function-calling code in `<entry-point>`.
- Add dependencies to `pyproject.toml` / `requirements.txt` (Python) or `*.csproj` (.NET).
- Change the model in `azure.yaml services.ai-project.deployments[]` before Step 8 provision.

If the user's original prompt was generic (no specific agent purpose described), skip customization and ship the sample as-is.

### Step 6 — Write the agent instruction file (required)

Do **not** skip this. The project needs a marker in its `AGENTS.md` (or `CLAUDE.md` in Claude Code) so future prompts reload this skill. If no such file exists, create it with the marker below. If the file already exists, weave the marker into the existing content naturally so it matches the file's structure and tone, rather than bluntly appending a standalone line that reads as out of place. Never overwrite the file or create a second one.

```
This project was built with the microsoft-foundry skill. Before working on or answering questions about foundry agents, read the microsoft-foundry skill first.
```

### Step 7 — Sanity-check the scaffold

Verify all four before continuing. If any check fails, pick **one** recovery path, then re-verify:

| Check | Expected | If failed |
|-------|----------|-----------|
| `azure.yaml services.ai-project.deployments[]` | Non-empty array with `name`, `model.{name,format,version}`, `sku.{name,capacity}` | Model resolution deferred — use recovery |
| Agent service `environmentVariables` `AZURE_AI_MODEL_DEPLOYMENT_NAME` (in `azure.yaml`) | Literal name **or** `${AZURE_AI_MODEL_DEPLOYMENT_NAME}` substitution | If literal `{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}` (double braces): use recovery |
| Agent service `codeConfiguration.entryPoint:` (in `azure.yaml`) | Matches a real file in `src/<agent-name>/` (e.g. `main.py` and `main.py` exists) | If mismatch (e.g. `entryPoint: app.py` but only `main.py` exists): edit `azure.yaml` to the real filename, then re-verify. Most often caused by passing a wrong `--entry-point` in Step 4. |
| `azure.yaml services:` keys | Only one `<agent-name>` entry | If `<agent-name>-2` exists: init was re-run; use recovery |

**Recovery paths** (pick based on whether Step 5 has already customized `src/<agent-name>/`):

1. **Hand-fix in place** *(use when Step 5 customization is already done — preserves user code)* — edit `azure.yaml services.ai-project.deployments[]` to add the model block, replace `{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}` in the agent service's `environmentVariables` with `${AZURE_AI_MODEL_DEPLOYMENT_NAME}`, then `azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME <deployment-name>`.
2. **Clean re-init** *(use only when Step 5 has not run yet — destructive: deletes `src/<agent-name>/`)* — delete `src/<agent-name>/`, remove the `services.<agent-name>:` block from `azure.yaml`, re-run Step 4.
3. **Interactive overwrite** *(loses Step 5 edits — re-resolves the model from the original manifest)* — re-run Step 4 *without* `--no-prompt`. When the collision prompt appears, **arrow-up to "Overwrite existing"** (default is *not* overwrite).

Never `azd env set AI_PROJECT_DEPLOYMENTS '[...]'` (single-escaped JSON breaks Bicep parse). Never `az cognitiveservices account deployment create` against this account (creates the deployment outside the azd lifecycle).

If recovery still fails → escape to [create-hosted.md](create-hosted.md).

### Step 8 — Provision Azure resources

> 🚦 **Project-selection gate (align with Step 2).** Only `azd provision` a new project when the user asked to create one. If the user gave an existing project, skip provision and use it. If the user didn't mention a project at all, stop and ask first — don't silently provision a new one.

```bash
azd provision --no-state --no-prompt
```

`--no-state` skips the existing-deployment check; safe here because the golden path starts from a fresh environment (Step 4). Keep it for this quickstart; you can omit it later when re-provisioning the same environment.

⏳ May take time — creates the resource group, Foundry account + project, model deployment, App Insights, Log Analytics. Wait for the prompt to return; do not interrupt.

### Step 9 — Wire local env vars

```bash
azd env get-values
```

Capture `FOUNDRY_PROJECT_ENDPOINT` and `AZURE_AI_MODEL_DEPLOYMENT_NAME`. Write `src/<agent-name>/.env`:

```env
FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project-name>
AZURE_AI_MODEL_DEPLOYMENT_NAME=<deployment-name>
```

Also mirror them into the azd env (so `azd ai agent run` injects the right values — it reads azd env *before* `.env`):

```bash
azd env set AZURE_AI_PROJECT_ENDPOINT "<endpoint>"
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME "<deployment-name>"
```

### Step 10 — Local smoke test

Set up a venv with `uv` installed first. `azd ai agent run` installs Python dependencies on first start; with an activated venv that has `uv` available, it uses `uv` (seconds) instead of plain `pip` (minutes).

> **Important:** the venv must live in `src/<agent-name>/` (next to `requirements.txt`). `azd ai agent run` resolves the venv relative to the service source directory; a venv at the project root is ignored and azd silently creates a second one without `uv`, wasting the speedup.

**Python:**
```bash
cd src/<agent-name>
python -m venv .venv
# Activate the venv — pick the line for your shell:
.\.venv\Scripts\Activate.ps1                    # Windows pwsh
source .venv/bin/activate                       # macOS / Linux
python -m pip install uv
cd -                                             # back to project root for the azd commands below
```

**.NET:** no pre-install step — `azd ai agent run` runs `dotnet restore` itself on first start.

The examples below use the default port `8088`. Confirm it is free; if occupied, choose another port and add the same `--port <n>` to both `run` and `invoke --local` below. Run the agent locally. For Python, do this **with the service-dir venv still activated** — activation is what lets `azd ai agent run` find `uv` for the fast dependency install. `azd ai agent run` **is** the local server — a foreground process holding the selected port that must stay alive from start, through every `invoke --local`, until you explicitly stop it.

Start it in a **managed** background session your shell tool can poll and stop (most tools detect a long-running foreground process and return a session/shell id — use that id). Do **not** use job operators (`bash &`, `nohup`, `start /B`, popped windows): on Linux/macOS the child gets `SIGHUP` and **dies when its parent bash exits**, so the next command sees `could not connect` even though `ss` from inside the *same* bash just showed `:8088` bound.

```bash
azd ai agent run --no-client
```

Poll a TCP connection to `localhost:<port>` every 2–5 seconds while the run session is alive; once connected, proceed to the smoke test. If the process exits or the startup timeout expires, inspect the server logs and resolve the cause before retrying.

Smoke-invoke (local):

```bash
azd ai agent invoke --local "<short representative prompt for the agent's purpose>"
```

Stop the local server via the managed session's stop primitive before continuing — a lingering process holds files in the project and breaks later cleanup.

### Step 11 — Deploy

Once local invocation succeeds, if the user does not explicitly ask to deploy, tell them the agent is ready and ask if they want to deploy. To deploy:

```bash
azd deploy --no-prompt
```

⏳ May take time — zips `src/<agent-name>/` (respecting `.agentignore`), uploads to Foundry, builds runtime remotely, registers agent version. Wait for the prompt to return; do not interrupt.

### Step 12 — Verify + remote smoke

```bash
azd ai agent show --output json
```

Expect `"status": "active"` (or `"deployed"`) and an `agent_endpoints` map.

Remote invoke (billed):

```bash
azd ai agent invoke "<short representative prompt>"
```

Run the smoke invocation only as part of the requested deployment or test.

### Step 13 — Submit eval suite generation (async, fire-and-forget)

> ⚠️ **Pre-summary gate.** Do not write the Step 14 final summary until this step has been submitted. The eval suite is part of the deployment artifact; skipping it ships an incomplete result.

Directly submit the eval suite generation asynchronously, do not ask the user for confirmation.

Read the agent service's `description:` from `azure.yaml` (the value you set in Step 5) and pass it as `--gen-instruction`:

```bash
azd ai agent eval generate --gen-instruction "<agent service description>" --no-wait --no-prompt
```

Expected output:

```
Eval generate submitted (async)
   dataset generation: datagen-<id> (queued)
   evaluator generation: evaluatorgen-<id> (in_progress)
   Config written to: src/<agent-name>/eval.yaml
   When ready, run:
     azd ai agent eval run
```

Generation runs server-side and takes several minutes. Tell the user:

> *"Eval suite generation submitted. Run `azd ai agent eval run` whenever you're ready — it'll wait for generation to finish and execute the eval in one step."*

Run `azd ai agent eval run` only after the user explicitly agrees.

### Step 14 — Final summary

Produce a concise summary covering: agent name/version/status/endpoints, a Playground link, the resources created, the eval suite generation submission, and the three follow-up commands below. Read `playground_url` directly from `azd ai agent show --output json`. If it is absent, construct the Playground URL from `azd env get-values`:

```
https://ai.azure.com/nextgen/r/{encodedSubId},{resourceGroup},,{accountName},{projectName}/build/agents/{agentName}/build?version={agentVersion}
```

`encodedSubId` = URL-safe base64 of the subscription GUID, padding stripped:

```bash
python -c "import base64,uuid;print(base64.urlsafe_b64encode(uuid.UUID('<SUBSCRIPTION_ID>').bytes).rstrip(b'=').decode())"
```

Three follow-up commands to include:

```bash
azd ai agent invoke "<follow-up message>"   # chat with the deployed agent (billed)
azd ai agent eval run                       # finalize + run the eval suite (Step 13)
azd down                                    # tear down all resources when done
```

## Error Handling

| Symptom | Fix |
|---------|-----|
| `azd ai agent init` fails with `--runtime must be one of: python_3_13, python_3_14, dotnet_10` | You passed a bare value like `python`. Use the full runtime token (e.g. `python_3_13`). |
| `azd ai agent init` fails with `--entry-point is required when using --deploy-mode code with --no-prompt` | Pass `--entry-point <filename>` matching the entry-point file the sample declares (from Step 3). |
| `codeConfiguration.entryPoint` doesn't match any file in `src/<agent-name>/` | You guessed the entry-point in Step 4. Edit the agent service in `azure.yaml` to the real filename (verify with `ls src/<agent-name>/`). No re-init needed. |
| `azd deploy` postdeploy hook fails with missing `AZURE_TENANT_ID` | Run `az account show --query tenantId -o tsv` and `azd env set AZURE_TENANT_ID <tenant-id>`, then re-run `azd deploy --no-prompt`. The deployed agent version from the first deploy is still valid; the postdeploy hook just registers env vars. |
| Scaffold sanity check fails (Step 7) | Pick a recovery path from Step 7. If still failing → [create-hosted.md](create-hosted.md). |
| Local invoke returns model `404` / wrong deployment | Stale `AZURE_AI_MODEL_DEPLOYMENT_NAME` in azd env overrides `.env`. Re-run Step 9 to sync both. |
| Anything else | Escape to [create-hosted.md](create-hosted.md). |

## Escape Hatch

If any step fails in a way not covered above, the output looks unexpected, or the user's request drifts outside what this quickstart covers → **stop improvising**. Read [create-hosted.md](create-hosted.md) and follow its full workflow.
