| name | healthmodel-integrate |
| description | Integrate a generated health model Bicep project into existing infrastructure-as-code. Detects IaC type (Bicep/Terraform/none), proposes integration level (NONE/SOME/FULL), generates integration snippets, verifies offline, and asks the user to deploy. WHEN: 'integrate health model into my infra', 'add health model to my bicep', 'connect health model to existing IaC', 'integrate health model bicep'. DO NOT USE FOR: designing entities (use healthmodel-design), deploying standalone health models (use healthmodel-deploy), discovering resources (use healthmodel-discovery), or Terraform module generation. |
Health Model IaC Integration
Integrate a generated health model Bicep project (.healthmodel/05-bicep/) into the user's existing infrastructure-as-code. The skill detects the IaC type, proposes an integration level, generates integration artifacts as snippets the user applies manually, verifies everything offline, and asks the user to test deployment themselves.
Integration levels
| Level | What it does | When to use |
|---|
| NONE | Health model Bicep stays standalone. Deploy separately via az deployment group create. | No Bicep IaC, Terraform projects, Azure Export Bicep, or user preference. |
| SOME | Parameterizes all resource references. Generates a .bicepparam file and a module-reference snippet for the user's entrypoint. | User has Bicep IaC and wants the health model to deploy alongside it with correct resource names. |
| FULL | Regenerates health model as an inline Bicep module with feature flags, object parameters, and conditional entity creation. | User wants deep integration where IaC feature flags control which health model entities exist. Matches the azure-search-openai-demo pattern. |
Rules
- ⛔ MANDATORY: Never modify the user's existing IaC files directly. Always generate snippets, diff previews, and
.bicepparam files for the user to apply. The user owns their IaC — the skill produces integration artifacts, not mutations.
- ⛔ MANDATORY: Never run the user's full deployment pipeline. Verify offline with
az bicep build. Ask the user to deploy and report results.
- ⛔ MANDATORY:
.healthmodel/05-bicep/ must exist before this skill runs. If missing, tell the user to run healthmodel-design first.
- ⛔ MANDATORY: If IaC detection is uncertain (ambiguous entrypoint, unknown framework, mixed Bicep+Terraform), default to NONE — the least invasive option.
- ⛔ MANDATORY: For FULL integration, do NOT use
loadJsonContent() for fields that need parameterization. Generate inline Bicep resource declarations instead. loadJsonContent() requires compile-time literal paths and cannot accept parameters.
- ⛔ MANDATORY: Verify BOTH the health model Bicep AND the user's entrypoint (if a snippet was generated) with
az bicep build.
- ⛔ MANDATORY: All generated files go under
.healthmodel/06-integrate/. Never write to user IaC directories.
- ⛔ MANDATORY: Present every integration change as a diff preview before asking the user to apply it.
- ⛔ MANDATORY: Avoid
dependsOn — use output references instead. Referencing a resource's output property (e.g. parentEntity.id) creates an implicit dependency that Bicep enforces at compile time. Explicit dependsOn is fragile, redundant, and hides the actual data flow.
Prerequisites
ls .healthmodel/05-bicep/main.bicep
az bicep version
command -v jq && echo "jq: ok"
Step 1: Verify health model Bicep exists
Check that .healthmodel/05-bicep/main.bicep exists and compiles:
az bicep build --file .healthmodel/05-bicep/main.bicep --stdout > /dev/null && echo "✓ health model Bicep compiles"
If it doesn't exist, stop and tell the user:
"No health model Bicep found. Run the design skill first: design entities and signals"
Step 2: Detect IaC type and entrypoint
Scan the project directory (and common infra subdirectories) for IaC files:
BICEP_FILES=$(find . -name '*.bicep' -not -path './.healthmodel/*' -not -path './node_modules/*' | head -20)
TF_FILES=$(find . -name '*.tf' -not -path './.healthmodel/*' -not -path './node_modules/*' | head -20)
EXPORT_MARKERS=$(grep -rl 'Generated by Azure Bicep\|az bicep export\|exported from Azure' . --include='*.bicep' 2>/dev/null | head -5)
Classify the IaC type:
| Detection | IaC Type | Default Level |
|---|
*.tf files found | Terraform | NONE |
Export markers found in *.bicep | Azure Export Bicep | NONE |
*.bicep files found (no export markers) | Bicep (hand-written) | SOME |
| Neither found | No IaC | NONE |
If Bicep is detected, analyze the entrypoint:
- Find the entrypoint: Look for
main.bicep files, or the single .bicep file at the repo root or in common dirs (infra/, deploy/, iac/, bicep/)
- Read
targetScope: Check if it's subscription, resourceGroup (default), managementGroup, or tenant
- Identify parameter style: Does it use
.bicepparam files, parameters.json, or inline params?
- Scan module conventions: Are there
modules/, core/, or similar subdirectories with reusable modules?
- Inventory resources: List the resource types declared, especially any that overlap with health model signal targets
Record all findings in .healthmodel/06-integrate/iac-detection.json:
{
"iacType": "bicep",
"entrypoint": "infra/main.bicep",
"targetScope": "subscription",
"parameterStyle": "bicepparam",
"moduleConventions": "infra/core/<category>/<resource>.bicep",
"exportBicep": false,
"resourceTypes": ["Microsoft.Web/sites", "Microsoft.CognitiveServices/accounts", ...],
"overlapWithDesign": ["Microsoft.Web/sites", "Microsoft.CognitiveServices/accounts"]
}
Step 3: Present detection and propose level
Present the detection results to the user and let them choose the integration level.
Use the ask_user tool:
{
"message": "I've analyzed your infrastructure-as-code. Here's what I found:\n\n**IaC Type**: <detected type>\n**Entrypoint**: <detected file or 'none found'>\n**Target Scope**: <scope>\n**Resource overlap with health model**: <count> resources\n\nBased on this, I recommend **<LEVEL>** integration.\n\nChoose your integration level:",
"requestedSchema": {
"properties": {
"integrationLevel": {
"type": "string",
"title": "Integration level",
"description": "NONE = standalone health model, deploy separately. SOME = parameterized module, deploys with your IaC with correct resource references. FULL = deep integration with feature flags and conditional entities (requires Bicep restructuring).",
"oneOf": [
{"const": "NONE", "title": "NONE — Standalone (deploy health model separately)"},
{"const": "SOME", "title": "SOME — Parameterized module (resource IDs as params)"},
{"const": "FULL", "title": "FULL — Deep integration (feature flags + conditional entities)"}
],
"default": "<detected recommendation>"
}
},
"required": ["integrationLevel"]
}
}
If the user chose a level higher than recommended (e.g., FULL when detection suggests SOME), warn them about the complexity and confirm. If they insist, proceed.
Step 4: NONE — Standalone integration
When level is NONE:
- Confirm the health model Bicep is self-contained (it already is from the design skill)
- Generate deployment instructions at
.healthmodel/06-integrate/deploy-standalone.md:
# Standalone Health Model Deployment
The health model Bicep is self-contained and deploys independently from your IaC.
## Deploy command
```bash
az deployment group create \
--resource-group <YOUR_RG> \
--template-file .healthmodel/05-bicep/main.bicep \
--parameters healthModelName=<NAME> location=<LOCATION>
```
## Why standalone?
<reason — Terraform detected / Azure Export Bicep / user preference / no IaC found>
- Tell the user why NONE was chosen:
- Terraform: "Your infrastructure uses Terraform. The health model is Bicep-only (Azure Monitor Health Models require ARM/Bicep). Deploy the health model separately using
az deployment group create."
- Azure Export Bicep: "Your Bicep appears to be exported from Azure (not hand-written). Export Bicep is often non-modular and literal-heavy. We recommend keeping the health model standalone. Consider refactoring your export Bicep to idiomatic Bicep before deeper integration."
- No IaC found: "No infrastructure-as-code detected in your project. The health model deploys standalone."
Skip to Step 7 (verification).
Step 5: SOME — Parameterized module integration
When level is SOME:
5a: Extract resource references
Scan all design JSON files in .healthmodel/03-design/ to find ARM resource IDs:
jq -r '.. | .resourceId? // empty' .healthmodel/03-design/signals/*.json 2>/dev/null | sort -u
jq -r '.. | .resourceId? // empty' .healthmodel/03-design/entities/*.json 2>/dev/null | sort -u
Collect all unique resource IDs and the parameters they map to (healthModelName, location, resource group).
5b: Generate parameterized module
Create .healthmodel/06-integrate/health-model-module.bicep — a wrapper that takes resource IDs as parameters and calls the health model:
// GENERATED FILE — edit .healthmodel/03-design instead, then re-run integration
// Integration level: SOME — parameterized module wrapper
@description('Name of the health model resource.')
param healthModelName string
@description('Azure region for the health model.')
param location string
@description('Resource ID of <resource 1>.')
param <resource1>ResourceId string
@description('Resource ID of <resource 2>.')
param <resource2>ResourceId string
// ... one param per unique resource reference
module healthModel '../05-bicep/main.bicep' = {
name: 'healthModel'
params: {
healthModelName: healthModelName
location: location
}
}
For SOME level, the inner main.bicep keeps loadJsonContent() — resource IDs are baked into the JSON from the discovery phase. The resource ID params on the wrapper are documentation-only — they make the module's dependencies explicit in the .bicepparam file so the deployment operator knows which resources must exist. They are NOT passed to the inner module.
5c: Generate .bicepparam file
Create .healthmodel/06-integrate/health-model.bicepparam:
using './health-model-module.bicep'
param healthModelName = '<from discovery>'
param location = '<from discovery>'
param <resource1>ResourceId = '<extracted ID>'
// ...
5d: Generate entrypoint snippet
Generate .healthmodel/06-integrate/snippet-for-main.bicep — the module reference the user should add to their entrypoint:
// ── Health Model ──
// Add this module reference to your entrypoint (e.g., infra/main.bicep)
module healthModel '<relative-path-to-health-model-module>' = {
name: 'healthModel'
scope: resourceGroup // adjust to match your targetScope
params: {
healthModelName: 'hm-${environmentName}'
location: location
<resource1>ResourceId: <module-output-or-resource-id>
// Map each param to your existing module outputs or resource IDs
}
}
5e: Present diff preview
Show the user:
- The files generated under
.healthmodel/06-integrate/
- The snippet they need to add to their entrypoint
- How to map parameters to their existing resources
Use ask_user to confirm they understand and will apply the snippet:
{
"message": "I've generated the integration artifacts. Here's what to do:\n\n1. Review the generated files in `.healthmodel/06-integrate/`\n2. Copy the module reference from `snippet-for-main.bicep` into your `<entrypoint>`\n3. Map the resource ID parameters to your existing module outputs\n\nDo you want me to explain the parameter mapping in detail?",
"requestedSchema": {
"properties": {
"understood": {
"type": "boolean",
"title": "I understand the integration steps",
"default": true
},
"needsHelp": {
"type": "boolean",
"title": "I need help mapping parameters to my existing resources",
"default": false
}
},
"required": ["understood"]
}
}
If needsHelp is true, walk through each parameter and suggest which existing module output or resource reference to use, based on the IaC detection in Step 2.
Step 6: FULL — Deep integration
When level is FULL:
6a: Analyze design for conditional entities
Read the health model design to identify:
- Which entities map to which resource types
- Which resources are "always present" vs "conditionally present" in the user's IaC
- Group entities by user journey or functional area
Cross-reference with the IaC detection (Step 2) to determine which resources have feature flags or conditional deployment in the user's existing Bicep.
6b: Design feature flags and object parameters
Following the azure-search-openai-demo pattern:
- Feature flags (bool params): One per conditionally-deployed resource group. Example:
deployAzureOpenAi, useApplicationInsights
- Resource ID params: One per monitored resource. Required params for always-present resources, optional (with
= '' default) for conditional ones
- Object params (if appropriate): Group related resources into typed objects for cleaner parameter passing
Document the parameter design in .healthmodel/06-integrate/param-design.json:
{
"featureFlags": [
{"name": "deployOpenAi", "type": "bool", "default": true, "controls": ["ai-inference-entity", "openai-signals"]},
{"name": "useAppInsights", "type": "bool", "default": false, "controls": ["app-perf-entity", "appinsights-signals"]}
],
"resourceIdParams": [
{"name": "appServiceResourceId", "type": "string", "required": true, "mapsTo": "backend-app entity"},
{"name": "cognitiveServicesResourceId", "type": "string", "required": false, "conditionedOn": "deployOpenAi"}
]
}
6c: Generate inline Bicep module
Create .healthmodel/06-integrate/health-model-full.bicep — a single-file module with:
- All parameters with
@description decorators
- Computed variables for canvas layout (child counts conditional on feature flags)
- Health model root resource
- Identity resources (UAMI + role assignments)
- Conditional entity resources with
if guards based on feature flags
- Signal definitions inline (NOT
loadJsonContent()) for signals whose resource references come from parameters
- Relationships using output references (implicit dependencies), NOT
dependsOn
Follow the reference pattern from azure-search-openai-demo/infra/core/monitor/health-model.bicep:
// GENERATED FILE — edit .healthmodel/03-design instead, then re-run integration
// Integration level: FULL — inline module with conditional entities
@description('Name of the health model resource.')
param name string
@description('Azure region.')
param location string
@description('Resource tags.')
param tags object = {}
// Feature flags
@description('Whether Azure OpenAI is deployed.')
param deployAzureOpenAi bool = true
// Resource IDs
@description('Resource ID of the App Service.')
param appServiceResourceId string
@description('Resource ID of Azure OpenAI. Required when deployAzureOpenAi is true.')
param cognitiveServicesResourceId string = ''
// Canvas layout — conditional child counts
var aiGroupChildCount = 1 + (deployAzureOpenAi ? 1 : 0)
var xAiGroup = 0
var xNextGroup = xAiGroup + aiGroupChildCount * 250
// Health model root
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: '${name}-identity'
location: location
tags: tags
}
resource healthModel 'Microsoft.CloudHealth/healthModels@2026-01-01-preview' = {
name: name
location: location
tags: tags
identity: { type: 'UserAssigned', userAssignedIdentities: { '${identity.id}': {} } }
properties: {}
}
// Conditional entity — only created when feature flag is true
resource aiInferenceEntity 'Microsoft.CloudHealth/healthModels/entities@2026-01-01-preview' = if (deployAzureOpenAi) {
parent: healthModel
name: 'ai-inference'
properties: {
displayName: 'AI Inference'
resourceId: cognitiveServicesResourceId
// ... inline properties from design JSON
}
}
6d: Generate entrypoint snippet
Same as Step 5d but with the FULL module and richer parameters:
module healthModel '<path>/health-model-full.bicep' = {
name: 'healthModel'
params: {
name: 'hm-${environmentName}'
location: location
tags: tags
deployAzureOpenAi: deployAzureOpenAi // from your existing feature flags
appServiceResourceId: backend.outputs.id
cognitiveServicesResourceId: deployAzureOpenAi ? openAi.outputs.resourceId : ''
}
}
6e: Present diff preview
Show the user the full module, the snippet, and how to map feature flags and resource IDs from their existing IaC. Use the same ask_user pattern as Step 5e.
Step 7: Verify offline
Run az bicep build to verify all generated Bicep compiles:
az bicep build --file .healthmodel/05-bicep/main.bicep --stdout > /dev/null \
&& echo "✓ health model Bicep compiles"
if [ -f .healthmodel/06-integrate/health-model-module.bicep ]; then
az bicep build --file .healthmodel/06-integrate/health-model-module.bicep --stdout > /dev/null \
&& echo "✓ integration module compiles"
fi
if [ -f .healthmodel/06-integrate/health-model-full.bicep ]; then
az bicep build --file .healthmodel/06-integrate/health-model-full.bicep --stdout > /dev/null \
&& echo "✓ full integration module compiles"
fi
If verification fails, show the error and fix the generated Bicep. Re-verify after every fix.
⛔ Do NOT run the user's full deployment. Verification is offline only.
Step 8: Ask user to test deployment
Tell the user what to do next and collect their results.
Use the ask_user tool:
{
"message": "Integration is ready! Here's what to do:\n\n1. Apply the snippet from `.healthmodel/06-integrate/` to your IaC entrypoint\n2. Run `az bicep build --file <your-entrypoint>` to verify it compiles\n3. Deploy using your normal deployment pipeline\n4. Come back and tell me how it went\n\nI won't run your deployment — that's your call.",
"requestedSchema": {
"properties": {
"deploymentStatus": {
"type": "string",
"title": "How did the deployment go?",
"oneOf": [
{"const": "success", "title": "Success — everything deployed correctly"},
{"const": "partial", "title": "Partial — some resources deployed, some failed"},
{"const": "failed", "title": "Failed — deployment did not succeed"},
{"const": "not_yet", "title": "Haven't deployed yet — I'll come back later"}
]
},
"details": {
"type": "string",
"title": "Details (error messages, what worked, what didn't)",
"description": "Paste any error messages or describe what happened. Leave blank if everything worked."
}
},
"required": ["deploymentStatus"]
}
}
Based on the response:
- success: Congratulate, suggest running
healthmodel-deploy's smoke test
- partial: Ask for error details, help diagnose, regenerate if needed
- failed: Ask for full error output, diagnose, and fix the integration artifacts
- not_yet: Tell them to come back when ready, remind them of the deploy steps
Step 9: Save integration state
Write the integration outcome to .healthmodel/06-integrate/integration-state.json:
{
"integrationLevel": "SOME",
"iacType": "bicep",
"entrypoint": "infra/main.bicep",
"generatedFiles": [
".healthmodel/06-integrate/health-model-module.bicep",
".healthmodel/06-integrate/health-model.bicepparam",
".healthmodel/06-integrate/snippet-for-main.bicep"
],
"verificationPassed": true,
"userDeploymentStatus": "success",
"integratedAt": "2026-05-24T10:00:00Z"
}
Adapting an existing integration
If .healthmodel/06-integrate/integration-state.json already exists:
- Read the previous integration level and type
- Ask the user if they want to keep the same level or change
- If the design has changed (check file timestamps), regenerate the integration artifacts
- Re-verify and ask the user to re-deploy