- name
- foundry-caphost-lifecycle
- description
- Day-2 lifecycle of Microsoft Foundry capability hosts — the operational layer on top of MS Learn's create-only guidance. Covers idempotent create (REST 2025-06-01), inspect/GET, delete, **soft-delete + purge of the parent Cognitive Services account** (the only op that releases the `serviceAssociationLink` on the agent subnet), redeploy guard for `Subnet already in use`, concurrent-op retry, idempotency rules, and soft-delete recovery (48h window). USE FOR: capability host lifecycle, caphost delete, caphost purge, soft-delete recover, serviceAssociationLink release, Subnet already in use, caphost idempotency, caphost 409 concurrent op, redeploy after teardown, az cognitiveservices account purge. DO NOT USE FOR: greenfield BYO-VNet Foundry deploy (use foundry-vnet-deploy), azd-template Foundry deploy (use threadlight-deploy or foundry-hosted-agents), spoke onboarding (use citadel-spoke-onboarding), tenant isolation (use azure-tenant-isolation).
- metadata
- {"version":"2.0.2"}
# Foundry Capability Host Lifecycle — Day-2 Operations
## 1. Goal
This skill teaches **Day-2 operations** for Microsoft Foundry capability hosts: how to
**inspect**, **idempotently re-create**, **delete**, **soft-delete the parent account**,
**purge**, and **redeploy** without tripping the `Subnet already in use` failure that
blocks every team that has ever torn down a VNet-injected Foundry account.
This is **explicitly not greenfield create** — for first-time BYO-VNet deploys see
[`foundry-vnet-deploy`](../foundry-vnet-deploy/SKILL.md), and for the azd template
flow see [`foundry-hosted-agents`](../foundry-hosted-agents/SKILL.md) or
[`threadlight-deploy`](https://github.com/aiappsgbb/threadlight-skills/blob/main/skills/threadlight-deploy/SKILL.md).
This skill is the field-experience overlay on top of the MS Learn capability-hosts
page ([learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts))
that ships with the operational knowledge MS Learn does not document.
## 2. When to use
Trigger this skill whenever any of these are true:
- You teardown'd a Foundry VNet-injected account and now `az deployment group create`
fails with `Subnet already in use` (or `SubnetIsFull` referencing a
`serviceAssociationLink`) when you try to redeploy into the **same** agent subnet.
- A caphost create is stuck in `Creating` for more than 10 minutes (the upstream
operation has a documented ~5-15 min p99; longer means hung — go to § 9).
- You need to **change the connections** referenced by a project capability host
(e.g., swap a Cosmos connection). MS Learn is explicit: updates are not supported;
delete + recreate is the only path.
- You need to fully reclaim the soft-deleted account name within the 48h window
(so a new account can be created with the **same** custom domain).
- You want a clean teardown that releases the `serviceAssociationLink` on the agent
subnet AND removes the hidden ACA Managed Environment that the capability host
provisioned under the covers.
- You have a soft-deleted account that needs to be **recovered** (not purged)
before the 48h window expires.
If none of those describe your situation and you're doing first-time create, stop
and use [`foundry-vnet-deploy`](../foundry-vnet-deploy/SKILL.md) instead.
## 3. What MS Learn covers vs what this skill adds
MS Learn ([capability-hosts](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts))
is the authoritative source for the REST API surface and create-time semantics.
Use it for the contract. This skill exists because MS Learn does **not** cover the
operational reality of Day-2.
| Concern | MS Learn | This skill |
|---|---|---|
| Account + project caphost REST shape (PUT/GET) | ✅ | references it |
| Idempotency rules (200/400/409 matrix) | ✅ | retry pattern (§ 6) |
| `One caphost per scope` constraint | ✅ | redeploy implications (§ 9) |
| `Updates not supported — delete and recreate` | ✅ | safe-replace flow (§ 7) |
| Account caphost prerequisite for project caphost | ✅ | inspect order (§ 5) |
| DELETE caphost REST endpoint | ✅ | what it does/doesn't free (§ 7) |
| **Deleting caphost releases the SAL on the agent subnet** | ❌ | **FALSE** — only purging the parent account does (§ 8, § 9) |
| **`az cognitiveservices account purge` semantics** | ❌ | full sequence (§ 8) |
| **48h soft-delete window + recovery** | ❌ | when to recover vs purge (§ 10) |
| **Hidden Managed ACA Environment created by caphost** | ❌ | only released on account purge (§ 8) |
| **Redeploy-after-teardown failure mode** | ❌ | the `Subnet already in use` guard (§ 9) |
| Concurrent-op 409 retry (`currently in non creating`) | ✅ pseudocode | runnable retry loop (§ 6) |
> **The single most important field-verified rule:** **deleting a capability host
> does NOT release the `serviceAssociationLink` on the agent subnet.** Only purging
> the parent `Microsoft.CognitiveServices/accounts` resource releases it. If you
> need to redeploy into the same subnet, you MUST purge — soft-delete alone is not
> enough. See § 8 and § 9 for the exact sequence and the symptom-to-fix mapping.
## 4. Constraints recap (from MS Learn)
Read these once. They drive every Day-2 decision below.
**Choose scope and setup mode first.** The explicit account-then-project
prerequisite below describes the Standard/BYO lifecycle, not a requirement to
add an account host to the canonical Basic private template. Basic creates
only an `Agents` **project** host with platform-managed stores. Public
platform-managed azd setup is a third route, not authority to skip a private
project host. Use the [shared hosted deployment preflight](../foundry-hosted-agents/references/deployment-preflight.md)
before registration. Missing Basic setup permits only a separately authorized
invocation of the existing Basic project-host module. Incompatible/failed/BYO
hosts require a decision, not automatic deletion or recreation.
| Constraint | Rule | Source |
|---|---|---|
| **One caphost per scope** | Each account, each project: only one active capability host. Second host with different name → 409 Conflict. | [MS Learn § Constraints](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **No updates** | There is no PATCH support. Configuration changes require DELETE + recreate. | [MS Learn § Constraints](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Account caphost prerequisite** | Standard/BYO explicit host lifecycle: verify the account host before the project host. Do not apply this as a customer-created account-host requirement to `basic-vnet`, whose canonical module creates only the project host. | [MS Learn § Constraints](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts), [Basic/Standard distinction](../foundry-vnet-deploy/SKILL.md#step-0-choose-the-template-decision-guide) |
| **Idempotency: same-name + same-config** | Learn documents 200; the GA schema permits 200/201 and live replay can return 201. Verify the same resource identity and `Succeeded` state. | [MS Learn § Idempotent behavior](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Idempotency: same-name + different config** | Returns 400 Bad Request. No silent in-place modification. | [MS Learn § Idempotent behavior](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Idempotency: different name at occupied scope** | Returns 409 Conflict (one-per-scope). | [MS Learn § Idempotent behavior](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Concurrent operation in flight** | Returns 409 `currently in non creating, retry after its complete`. Retry with backoff. | [MS Learn § Concurrent operations](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Permissions to create / delete caphost** | `Contributor` on the Foundry account. | [MS Learn § Prerequisites](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Permissions to wire BYO connections** | `User Access Administrator` or `Owner` (for assigning RBAC on the BYO resources). | [MS Learn § Prerequisites](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **API version** | `2025-06-01` is the current canonical version this skill targets. | [MS Learn REST examples](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
| **Project caphost connection refs are by name, not resource ID** | `threadStorageConnections` / `vectorStoreConnections` / `storageConnections` / `aiServicesConnections` are arrays of **connection names** that already exist on the project. | [MS Learn § Required properties](https://learn.microsoft.com/azure/foundry/agents/concepts/capability-hosts) |
## 5. Inspect: query caphost state before any change
> **Always inspect before mutating.** The one-per-scope constraint and the
> no-update rule mean an idempotent PUT only behaves "idempotently" if you know
> what's already there. Read first, decide second.
### 5.1 List account-level capability hosts
```http
GET https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{accountName}/capabilityHosts?api-version=2025-06-01
```
Equivalent `az` (works for any user with `Cognitive Services Contributor`):
```bash
az rest --method get \
--url "https://management.azure.com/subscriptions/${SUB}/resourceGroups/${RG}/providers/Microsoft.CognitiveServices/accounts/${ACCT}/capabilityHosts?api-version=2025-06-01" \
--query "value[].{name:name, state:properties.provisioningState, kind:properties.capabilityHostKind}"
```
Expected shapes:
- `value: []` — no explicit account caphost exists. Standard/BYO is blocked
pending an authorized setup decision. Basic does not require creating one;
inspect its **project** inventory independently.
- `value: [{name: "default", state: "Succeeded", kind: "Agents"}]` — healthy.
Safe to operate on the project caphost.
- `value: [{state: "Creating"}]` — operation in flight. Do NOT issue another PUT;
poll the operation result (§ 5.3) until terminal.
- `value: [{state: "Failed"}]` — blocked. Preserve it and collect its exact GET/error.
DELETE/recreate (§ 7) needs separate explicit authorization, never a preflight repair.
### 5.2 List project-level capability hosts
```bash
az rest --method get \
--url "https://management.azure.com/subscriptions/${SUB}/resourceGroups/${RG}/providers/Microsoft.CognitiveServices/accounts/${ACCT}/projects/${PROJ}/capabilityHosts?api-version=2025-06-01" \
--query "value[].{name:name, state:properties.provisioningState, kind:properties.capabilityHostKind, thread:properties.threadStorageConnections, vector:properties.vectorStoreConnections, storage:properties.storageConnections, ai:properties.aiServicesConnections}"
```
Before hosted registration, read the returned project host by its actual name
and require `Agents` / `Succeeded`. An account `Succeeded` response is not this
check. For Basic, expect empty/absent BYO arrays; for Standard, compare exact
approved connection names. An unsuccessful GET is not an empty inventory.
### 5.3 Poll an in-flight operation
Persist intent and native response headers/operation identity before parsing,
using the [shared custody vocabulary](../foundry-hosted-agents/references/operation-recovery.md).
Give each GET a finite I/O timeout within the observation budget. A timeout or
generic404 alone does not prove failure, absence or permission to replay.
The verb-specific final-state checks below are not a generic response-recovery
rule; retain the exact target, caller/read identity and API version.
The GA 2025-06-01 Swagger uses different LRO contracts by verb:
- PUT 201 uses `Azure-AsyncOperation`; its declared final state is the
original capability-host URI.
- DELETE 202 uses `Location` and `Retry-After`; its declared final state is
the `Location` response.
For PUT, poll the original URI for up to 15 minutes until
`properties.provisioningState` is `Succeeded` or `Failed`. For DELETE,
poll `Location`, honor `Retry-After`, require terminal `Succeeded`, and
then verify the original URI returns 404. The live provider can remove the
`Location` result immediately after completion; in that case a `Location`
404 is only indeterminate and counts as success solely when the original
capability-host URI also returns 404. **Do not issue a parallel PUT
or DELETE on the same scope while an operation is `Running`** — you'll
get the `currently in non creating, retry after its complete` 409 (§ 6).
## 6. Create: idempotent PUT pattern with retry
### 6.1 Account capability host
```http
PUT https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{accountName}/capabilityHosts/{name}?api-version=2025-06-01
{
"properties": {
"capabilityHostKind": "Agents"
}
}
```
(For VNet-injected accounts, the `customerSubnet` is set at deploy time by
[`foundry-vnet-deploy`](../foundry-vnet-deploy/SKILL.md). This skill operates on
the caphost after that subnet binding already exists; it does not re-bind the
subnet.)
### 6.2 Project capability host
```http
PUT https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}/capabilityHosts/{name}?api-version=2025-06-01
{
"properties": {
"capabilityHostKind": "Agents",
"threadStorageConnections": ["my-cosmosdb-conn"],
"vectorStoreConnections": ["my-aisearch-conn"],
"storageConnections": ["my-storage-conn"],
"aiServicesConnections": ["my-azure-openai-conn"]
}
}
```
This is the **Standard/BYO** shape, not the Basic project-host body. Basic uses
only `capabilityHostKind: Agents`; reuse the
[canonical module](../foundry-vnet-deploy/templates/basic-vnet/modules-network-secured/add-project-capability-host.bicep),
never copy Standard arrays into it or overwrite an existing BYO host.
The four `*Connections` arrays are **connection names** that already exist on the
project (or are inherited from account-level), per MS Learn § "Project capability
host required properties". Wrong-name → 400. Resource IDs in place of names → 400.
### 6.3 The retry-on-409 contract
Three distinct 409 responses, three different handling rules (per MS Learn
§ "HTTP 409 Conflict errors"):
```bash
for attempt in $(seq 1 6); do
if result=$(az rest --method put \
--url "https://management.azure.com/subscriptions/${SUB}/resourceGroups/${RG}/providers/Microsoft.CognitiveServices/accounts/${ACCT}/capabilityHosts/${NAME}?api-version=2025-06-01" \
--body '{"properties":{"capabilityHostKind":"Agents"}}' 2>caphost.err); then
printf '%s\n' "$result"
break
fi
if grep -qi "currently in non creating" caphost.err && [[ "$attempt" -lt 6 ]]; then
sleep 30
continue
fi
cat caphost.err >&2
exit 1
done
```
SDK support for capability host management isn't available. Use REST
directly; `azure-mgmt-cognitiveservices` 14.1.0 contains resource models
but no capability-host operation group.
### 6.4 What a replay looks like
Microsoft Learn says a same-name, same-body replay returns **200 OK**.
The published GA Swagger permits both 200 and 201, and live validation
observed 201 on repeated identical PUTs. Treat 200 or 201 as transport
success, then GET the resource and require the same resource ID, name,
configuration, and `Succeeded` state. Do not interpret 201 alone as proof
that a second resource was created.
## 7. Delete: caphost-only (lightweight, keeps account)
### 7.1 When to use this path
DELETE the capability host (without deleting the parent account) when:
- You need to **change a connection reference** (Cosmos, Search, Storage, AOAI)
on a project caphost. MS Learn is explicit that updates are not supported, so
the only path is DELETE + recreate.
- You're tearing down agents for a project but want to keep the Foundry account,
models, other projects, etc.
- The caphost is in `Failed` or stuck `Updating` state and you need a clean slate.
### 7.2 DELETE caphost REST
```http
DELETE https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{accountName}/capabilityHosts/{name}?api-version=2025-06-01
```
(For project scope, insert `projects/{projectName}/` between `accounts/{accountName}/`
and `capabilityHosts/`.)
The response is 202 with `Location` and `Retry-After` headers. Poll per § 5.3 until
`Succeeded` (typical: 30s-2min; p99 ~5min). Then verify with GET — expect 404.
```bash
# verify gone
az rest --method get \
Ver en GitHub