AmyDocs

Deploy the backend

Amy's backend runs in your Azure subscription, with your AI provider and credentials. It is published as a private container image; you don't build anything:

amyvalorconsulting.azurecr.io/amy-backend:<version>

Pulling the image needs your organization's registry credentials, a user and a password. Get them in Amy: Organization settings > Amy AI > Backend settings > Set up a backend > Get access to the backend image (organization administrators). If your Amy doesn't show it yet, ask at contact@valor-co.com and name your Azure DevOps organization. Keep them secret; they are for your organization only.

Quick start: the deployment form

  1. Get the registry user and password in Amy, as above.
  2. Open the deployment form in the Azure portal. Choose a subscription and a resource group, then fill in the backend's name, your organization, your AI provider and the registry user and password.
  3. Select Review + create. When the deployment is done, its Outputs show the backend's address.
  4. In Amy: Organization settings > Amy AI > Backend settings, enter that address, select Test connection and save.

The form creates an App Service plan (B1 unless you choose another size), the App Service running Amy's backend, a Key Vault holding your AI provider's key and the backend's session key, and a managed identity that can read only those secrets. Users sign in with their Azure DevOps account; no app registration is needed. You need Owner, or Contributor and User Access Administrator, on the resource group.

With the Azure CLI instead (it asks for the key and the password):

az deployment group create --resource-group "<resource-group>" --template-uri "https://amydocs.valor-co.com/deploy/backend.json" `
  --parameters appName="<app>" organization="<organization>" aiProviderBaseUrl="<chat-completions URL>" aiProviderModel="<model>" registryUsername="<registry-user>"

A backend from the form has no Amy administrators yet: settings only administrators may change, such as personas and usage monitoring, stay unavailable until a later backend version. For those today, and for the optional features below, set the backend up by hand with the rest of this guide.

This guide deploys it on Azure App Service for Containers, with secrets in Azure Key Vault and a managed identity. About 30 minutes; you need Contributor and User Access Administrator (or Owner) on a resource group, and the right to create an app registration in Microsoft Entra ID.

How the parts connect:

To see the published versions:

az acr repository show-tags --name amyvalorconsulting --repository amy-backend --username "<registry-user>" --password "<registry-password>" -o table

Replace the placeholders in the commands: <version> (the newest in that list), <registry-user> and <registry-password>, <resource-group>, <location>, <app> (globally unique; the backend URL becomes https://<app>.azurewebsites.net), <plan>, <key-vault>, <tenant-id>, <client-id>, <organization> (the name in https://dev.azure.com/<organization>). The commands are for PowerShell with the Azure CLI.

1. Register the backend in Microsoft Entra ID

Users sign in to the backend with their work account.

  1. In the Microsoft Entra admin center, create an app registration (single tenant), for example "Amy backend". Note the Application (client) ID (<client-id>) and Directory (tenant) ID (<tenant-id>).
  2. Add a Web redirect URI: https://<app>.azurewebsites.net/auth/callback.
  3. Create a client secret. You store it in Key Vault in step 3; don't save it anywhere else.
  4. Under API permissions, check that Microsoft Graph openid and profile (delegated) are listed, and add Azure DevOps delegated permissions vso.profile, vso.wiki, vso.work and vso.code (all read-only; not user_impersonation). The backend uses vso.profile once at sign-in to check that the user belongs to your Azure DevOps organization, and the others only for the MCP server, acting as the signed-in user.
  5. Select Grant admin consent for . This needs a Cloud Application Administrator, Application Administrator or Global Administrator. Without it, users are asked for admin approval when they sign in.

Optional: define an app role (for example Amy.User), assign it to users or groups, and set REQUIRED_APP_ROLE in step 4 to limit who may use Amy.

2. Create the App Service and the Key Vault

az login
az group create --name "<resource-group>" --location "<location>"
az appservice plan create --name "<plan>" --resource-group "<resource-group>" --is-linux --sku B1
az webapp create --name "<app>" --resource-group "<resource-group>" --plan "<plan>" `
  --container-image-name "amyvalorconsulting.azurecr.io/amy-backend:<version>" `
  --container-registry-url "https://amyvalorconsulting.azurecr.io" `
  --container-registry-user "<registry-user>" --container-registry-password "<registry-password>"
az webapp update --name "<app>" --resource-group "<resource-group>" --https-only true
az webapp config set --name "<app>" --resource-group "<resource-group>" --min-tls-version 1.2 --ftps-state Disabled --always-on true
$appId = az webapp show --name "<app>" --resource-group "<resource-group>" --query id -o tsv
az resource update --ids "$appId/config/web" --set properties.healthCheckPath=/api/health

az webapp identity assign --name "<app>" --resource-group "<resource-group>"
$principal = az webapp identity show --name "<app>" --resource-group "<resource-group>" --query principalId -o tsv
az keyvault create --name "<key-vault>" --resource-group "<resource-group>" --location "<location>" --enable-rbac-authorization true
$kvId = az keyvault show --name "<key-vault>" --query id -o tsv
az role assignment create --role "Key Vault Secrets User" --assignee-object-id $principal --assignee-principal-type ServicePrincipal --scope $kvId
$me = az ad signed-in-user show --query id -o tsv
az role assignment create --role "Key Vault Secrets Officer" --assignee-object-id $me --assignee-principal-type User --scope $kvId

App Service keeps the registry credentials in its settings and uses them for every pull. Role assignments can take a few minutes to apply.

3. Store the secrets

Type each value at the prompt, so it never appears in your command history:

function Set-AmySecret($name) {
  $secure = Read-Host -AsSecureString "Value for $name"
  $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure))
  az keyvault secret set --vault-name "<key-vault>" --name $name --value $plain --output none
}
Set-AmySecret "ai-provider-api-key"     # your AI provider's key
Set-AmySecret "entra-client-secret"     # the client secret from step 1
Set-AmySecret "session-signing-key"     # 32+ random characters, see below

For the session signing key, generate a value and paste it at the prompt:

$bytes = New-Object byte[] 48; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes); [Convert]::ToBase64String($bytes)

4. Configure the backend

$kv = "https://<key-vault>.vault.azure.net/secrets"
az webapp config appsettings set --name "<app>" --resource-group "<resource-group>" --output none --settings `
  "WEBSITES_PORT=3001" `
  "AUTH_MODE=entra" `
  "PUBLIC_BASE_URL=https://<app>.azurewebsites.net" `
  "ENTRA_TENANT_ID=<tenant-id>" `
  "ENTRA_CLIENT_ID=<client-id>" `
  "ENTRA_CLIENT_SECRET=@Microsoft.KeyVault(SecretUri=$kv/entra-client-secret/)" `
  "SESSION_SIGNING_KEY=@Microsoft.KeyVault(SecretUri=$kv/session-signing-key/)" `
  "ALLOWED_ORGANIZATIONS=<organization>" `
  "ALLOWED_ORIGINS=https://valorconsulting.gallerycdn.vsassets.io" `
  "AI_PROVIDER=openai-compatible" `
  "AI_PROVIDER_BASE_URL=<chat-completions URL of your provider>" `
  "AI_PROVIDER_MODEL=<model or deployment name>" `
  "AI_PROVIDER_AUTH_HEADER=<header your provider expects, e.g. api-key or authorization>" `
  "AI_PROVIDER_API_KEY=@Microsoft.KeyVault(SecretUri=$kv/ai-provider-api-key/)"
az webapp restart --name "<app>" --resource-group "<resource-group>"

5. Verify

Invoke-RestMethod "https://<app>.azurewebsites.net/api/health"   # status "healthy" and the backend version
Invoke-RestMethod "https://<app>.azurewebsites.net/api/ready"    # succeeds when the AI provider is configured

In the portal, App Service > Settings > Environment variables shows a green Key Vault Reference status for each secret.

6. Connect your Azure DevOps organization

An organization administrator (Project Collection Administrators) opens Amy in any project, selects the Amy logo, then Backend settings, enters https://<app>.azurewebsites.net, selects Test connection and saves. Each user then confirms the backend once and signs in. Details: ADMIN-CONFIGURATION.md.

Updating

New backend versions are published as new image tags; the start of this guide shows how to list them. Pin a version, and move to a new one deliberately:

az webapp config container set --name "<app>" --resource-group "<resource-group>" --container-image-name "amyvalorconsulting.azurecr.io/amy-backend:<new-version>"
az webapp restart --name "<app>" --resource-group "<resource-group>"

With web search, the backend is the container named main. Update both containers to the new version instead:

az webapp sitecontainers update --name "<app>" --resource-group "<resource-group>" --container-name main --image "amyvalorconsulting.azurecr.io/amy-backend:<new-version>" `
  --registry-username "<registry-user>" --registry-password "<registry-password>"
az webapp sitecontainers update --name "<app>" --resource-group "<resource-group>" --container-name searxng --image "amyvalorconsulting.azurecr.io/amy-searxng:<new-version>" `
  --registry-username "<registry-user>" --registry-password "<registry-password>"
az webapp restart --name "<app>" --resource-group "<resource-group>"

/api/health reports the running version. The extension and the backend are versioned independently; the extension works with any backend that offers the API version it needs and explains features a backend doesn't offer.

Usage and cost controls

Your organization pays for its own AI usage. Set these as app settings; the defaults apply when a setting is absent:

Setting Default Purpose
LIMIT_REQUESTS_PER_USER_PER_MINUTE 10 Request rate per user
LIMIT_REQUESTS_PER_USER_PER_DAY 200 Requests per user and day
LIMIT_REQUESTS_PER_ORG_PER_DAY 2,000 Requests per organization and day
LIMIT_REQUESTS_GLOBAL_PER_MONTH 50,000 All requests per month
LIMIT_TOKENS_PER_USER_PER_DAY 0 (none) Tokens per user and day, as reported by your provider
LIMIT_TOKENS_PER_ORG_PER_DAY / LIMIT_TOKENS_PER_ORG_PER_MONTH 0 (none) Tokens per organization
MAX_CONCURRENT_REQUESTS / MAX_CONCURRENT_REQUESTS_PER_ORG 10 / 4 Concurrent requests
AI_PROVIDER_MAX_OUTPUT_TOKENS unset Output tokens per request
AI_DISABLED false Emergency switch: refuses all AI requests

Counters are kept in memory: run one instance (don't scale the App Service plan out), or each instance counts separately. Logs contain metadata only (operation, organization, pseudonymous user, duration, outcome, token counts), never prompts, content or credentials.

Optional: pull-request documentation checks

Amy can review completed pull requests on its own and propose Wiki updates for approval (USER-GUIDE.md). The backend then needs storage for proposals and read access to Azure DevOps.

  1. Storage (no account keys; the app identity gets access to one container):

    az storage account create --name "<storage>" --resource-group "<resource-group>" --location "<location>" --sku Standard_LRS --min-tls-version TLS1_2 --allow-shared-key-access false --allow-blob-public-access false
    $st = az storage account show --name "<storage>" --resource-group "<resource-group>" --query id -o tsv
    az role assignment create --role "Storage Blob Data Contributor" --assignee-object-id $me --assignee-principal-type User --scope $st
    az storage container create --account-name "<storage>" --name "amy-state" --auth-mode login
    az role assignment create --role "Storage Blob Data Contributor" --assignee-object-id $principal --assignee-principal-type ServicePrincipal --scope "$st/blobServices/default/containers/amy-state"
    
  2. Hook secret: Set-AmySecret "pr-docs-hook-secret" with 32+ random characters (generated as in step 3).

  3. Settings:

    az webapp config appsettings set --name "<app>" --resource-group "<resource-group>" --output none --settings `
      "PR_DOCS_ENABLED=true" `
      "PR_DOCS_HOOK_SECRET=@Microsoft.KeyVault(SecretUri=$kv/pr-docs-hook-secret/)" `
      "ADO_AUTH_MODE=managed-identity" `
      "STATE_STORE=blob" `
      "STATE_BLOB_CONTAINER_URL=https://<storage>.blob.core.windows.net/amy-state"
    
  4. Azure DevOps access for the backend: in Organization settings > Users, add the App Service's managed identity ($principal) with Basic access. Grant it Read on the repositories and Wikis to check, and Contribute to pull requests on those repositories (for the reviewer notification). Nothing else.

  5. Who may change the checks: add the app role Amy.Admin to the app registration from step 1 and assign it to those people.

  6. Choose repositories and reviewers in Amy: Amy logo > Backend settings > Pull-request documentation checks.

  7. Service hook, in each project: Project settings > Service hooks > + > Web Hooks, event Pull request updated, URL https://<app>.azurewebsites.net/api/hooks/pull-request, HTTP header x-amy-hook-secret:<the pr-docs-hook-secret value>, resource details All.

Optional: MCP server for AI clients

AI clients that support the Model Context Protocol (for example VS Code) can use Amy's tools at https://<app>.azurewebsites.net/mcp. Users sign in with Entra ID and Azure DevOps is read with their own permissions; anything that would change content becomes a proposal they approve in Amy.

  1. On the app registration: under Expose an API, set the Application ID URI (api://<client-id>) and add the delegated scope Amy.Mcp; in the manifest set "accessTokenAcceptedVersion": 2.
  2. Set MCP_ENABLED=true, plus STATE_STORE and STATE_BLOB_CONTAINER_URL as for pull-request checks.
  3. In the client, use the URL and the header x-azdo-organization: <organization>.

Questions can search the web when the Wiki doesn't answer them, or when the user asks for it (backend 0.11.2 or later). The backend uses SearXNG, a free, self-hosted metasearch engine that passes the question to public search engines (for example Bing, DuckDuckGo, Brave and Wikipedia). It runs as a second container, a sidecar, in the backend's App Service: only the backend reaches it, at http://localhost:8888, and it needs no other Azure resources. The amy-searxng image is SearXNG with the settings the backend needs; use the backend's version.

  1. Secret: Set-AmySecret "searxng-secret" with 32+ random characters (generated as in step 3).

  2. Sidecar mode: an app with a single container takes sidecars once it's converted. The backend becomes the container named main, with its image and port unchanged:

    az webapp sitecontainers convert --mode sitecontainers --name "<app>" --resource-group "<resource-group>"
    az webapp sitecontainers update --name "<app>" --resource-group "<resource-group>" --container-name main --image "amyvalorconsulting.azurecr.io/amy-backend:<version>" `
      --registry-username "<registry-user>" --registry-password "<registry-password>"
    
  3. The SearXNG container:

    az webapp sitecontainers create --name "<app>" --resource-group "<resource-group>" `
      --container-name searxng --image "amyvalorconsulting.azurecr.io/amy-searxng:<version>" --target-port 8888 `
      --registry-username "<registry-user>" --registry-password "<registry-password>"
    
  4. Settings. App settings reach every container, so SearXNG reads its secret from here:

    az webapp config appsettings set --name "<app>" --resource-group "<resource-group>" --output none --settings `
      "WEB_SEARCH_PROVIDER=searxng" `
      "WEB_SEARCH_BASE_URL=http://localhost:8888" `
      "SEARXNG_SECRET=@Microsoft.KeyVault(SecretUri=$kv/searxng-secret/)"
    az webapp restart --name "<app>" --resource-group "<resource-group>"
    
  5. Check: turn on Web search in Amy's settings and ask "Search the web for …". App Service > Log stream shows Web search completed with the number of results. With statusCategory: network, the SearXNG container isn't running; its log: az webapp sitecontainers log --name "<app>" --resource-group "<resource-group>" --container-name searxng.

Only the question leaves the backend: SearXNG passes it to the search engines without user information, and they see the App Service's outbound address. Search engines may block data-center addresses, Google most often; SearXNG then pauses that engine for a while and answers from the others. SearXNG is open source under the AGPL-3.0 license (source); the amy-searxng image adds only its settings.

Optional: features that read your code or search your content

These stay off until you set them. All need STATE_STORE=blob and STATE_BLOB_CONTAINER_URL as for pull-request checks. The features marked "own access" also need the backend's own Azure DevOps access (ADO_AUTH_MODE=managed-identity, as for pull-request checks).

Setting Feature Needs
ARCHITECTURE_DOCS_ENABLED=true Generate Project Documentation and Repository Documentation: Check, and with them Debate Architecture and Review a Pull Request once a project's architecture documentation exists. ARCHITECTURE_MAX_UPLOAD_BYTES limits the code sent per generation (25,000,000 characters by default, at most 60,000,000). Sends source code and Wiki content to your AI provider. The extension reads the repositories with each user's own permissions.
REPO_DOCS_ENABLED=true Repository Documentation: reads all of a repository's code and opens a pull request with its documentation set. Own access, with Read, Contribute, Create branch and Contribute to pull requests on the repositories administrators enable.
CODE_AGENT_ENABLED=true Code with Amy: from a task or a work item to a pull request ready for review, after a person approves the plan. Only organization administrators and the teams they allow see it. Own access, with Read, Contribute, Create branch and Contribute to pull requests on the repositories administrators enable, and View builds and Queue builds on the pipelines that validate their pull requests. AI_PROVIDER=anthropic-vertex.
MOCKUP_DESIGNER_ENABLED=true Mockup Designer. Own access. Its agent needs Claude (AI_PROVIDER=anthropic-vertex).
MOCKUP_BROWSER=server, MOCKUP_BROWSER_ENDPOINT=wss://… Lets the Mockup Designer open your test environments in a Playwright browser server you run. A browser server of the same Playwright version as the backend.
DELIGHT_CHECK_ENABLED=true Delight Check: every UI page found in the code scored on Product Delight. Sends UI code to your AI provider.
HOW_IT_WORKS_ENABLED=true How It Works Today: the application's screens described from the code. Sends UI and backend code to your AI provider.
AZURE_SEARCH_ENDPOINT=https://… Ask with search and Diagnose a Ticket. Own access, and Azure AI Search with the roles Search Service Contributor and Search Index Data Contributor for the managed identity.
GRAFANA_URL=https://… Logs and traces for Diagnose a Ticket and Ask. The Grafana Viewer role for the managed identity.

Design Requirements, Access by tab and sprint documents at the end of each sprint need STATE_STORE=blob; Access by tab and sprint documents also need the backend's own access. Administrators then set each feature up under Organization settings > Amy AI.

Rotating secrets

  1. Create a new version of the secret in Key Vault (ai-provider-api-key, entra-client-secret, session-signing-key, pr-docs-hook-secret or searxng-secret). The app settings don't change: the references always use the latest version.
  2. Restart the app (az webapp restart ...) to use it immediately; otherwise App Service picks it up within a day.
  3. Check that AI requests work, then revoke the old credential at its source. Rotating session-signing-key signs everyone out; rotating pr-docs-hook-secret needs the new value in each service hook.

Networking

The defaults above give: HTTPS only with TLS 1.2+, Entra sign-in required, CORS limited to Amy's origin, Key Vault readable only by the app identity, and outbound HTTPS from App Service to your AI provider (with web search, also from SearXNG to the public search engines).

Where required, add App Service VNet integration (with all outbound traffic routed through the VNet) for a private AI endpoint or Key Vault, a Key Vault private endpoint with the privatelink.vaultcore.azure.net DNS zone, and access restrictions on the backend. Behind a proxy, set HTTPS_PROXY and NODE_USE_ENV_PROXY=1.

Troubleshooting

Symptom Check
The app doesn't start; the log stream names a setting Fill that setting. Placeholders such as <app> are rejected.
unresolved Key Vault reference The secret exists, the app identity has Key Vault Secrets User, the vault name in the reference is right, and the network can reach the vault.
/api/ready fails The AI provider settings are incomplete.
Test connection in Amy fails with "could not be reached" The URL starts with https:// and has nothing after the host; ALLOWED_ORIGINS is exactly as in step 4.
Users are asked for admin approval when signing in Step 1.5: grant admin consent.
"The AI provider could not be reached" Outbound access to the provider, the URL, or a proxy (see Networking).
The container doesn't start, and the log shows unauthorized or an image pull failure The registry user or password is wrong or no longer valid. Ask for new credentials at contact@valor-co.com, then set them with az webapp config container set --container-registry-user … --container-registry-password … (with web search, az webapp sitecontainers update --container-name main --registry-username … --registry-password …).
Web search failed with status: 403 in the log The SearXNG container must use the amy-searxng image: plain SearXNG refuses the backend's JSON requests.
AI requests fail with HTTP 400 for newer Claude models Leave AI_PROVIDER_TEMPERATURE empty: Claude 4.7 and later reject it.