Agentic DevOps Starter

August 24, 2026 · View on GitHub

architecture_diagram

A hands-on starter for building, running, and deploying an AI-powered full-stack application with Agentic DevOps practices.

The project combines a GitHub Copilot SDK backend, AG-UI SSE streaming, a React + TypeScript frontend, Terraform-managed Azure infrastructure, and GitHub Actions CI/CD.

Overview

AreaCurrent implementation
AI runtimeGitHub Copilot SDK sessions (default) or Azure AI Foundry BYOK sessions, managed by a FastAPI backend
UI protocolAG-UI-style Server-Sent Events (SSE) stream from POST / or POST /v1/byok/foundry
FrontendReact 18, TypeScript, Vite, Zustand, Tailwind CSS
Agent patternsSingle chat, multi-turn conversation, Fleet, Infinite Session, and multi-agent teams
Tool callingBuilt-in tools (tools.py) and optional remote MCP server (mcp_client.py) via MCP_SERVER_URL
File uploadsAzure Blob Storage-backed uploads with size/type validation
InfrastructureAzure App Service, ACR, Storage Account, VNet, Blob private endpoint, Log Analytics
DeliveryGitHub Actions CI and Azure App Service deployment with OIDC

Architecture

Browser
  -> React/Vite frontend
  -> /api/* through Vite dev proxy or nginx production proxy
  -> FastAPI backend (:5100)
  -> GitHub Copilot SDK subprocess/session
  -> GitHub Copilot

Azure deployment:
GitHub Actions -> ACR -> Azure App Service container (:8080)
                             |-> nginx serves frontend and proxies /api/*
                             |-> FastAPI backend runs on :5100
                             |-> OTel Collector sidecar runs on :4318 (Copilot CLI telemetry)
                             |-> supervisor manages all three processes

Project Structure

agentic-devops-starter/
├── app/
│   ├── agui_server.py              # FastAPI app factory and server entry point
│   ├── agui_client.py              # CLI smoke-test client
│   ├── src/
│   │   ├── api/                    # Routes, auth (OAuth), request/response models, SSE helpers
│   │   ├── core/                   # Pydantic settings, logging, observability
│   │   ├── runtime/                # Copilot client/session pool, jobs, skills, MCP client, tools, isolation
│   │   ├── storage/                # Azure Blob Storage and upload validation
│   │   └── teams/                  # Multi-agent team execution and pattern definitions
│   ├── frontend/                   # React + TypeScript + Vite frontend
│   ├── tests/                      # pytest test suite
│   ├── pyproject.toml              # uv-managed Python project
│   ├── .env.example                # Local environment reference
│   └── Dockerfile.appservice       # Production App Service container
├── infra/
│   ├── main.tf                     # Terraform orchestration
│   ├── acr/                        # Azure Container Registry
│   ├── app-service/                # Linux Web App
│   ├── app-service-plan/           # App Service Plan
│   ├── log-analytics/              # Log Analytics Workspace
│   ├── network/                    # VNet and subnets
│   └── storage/                    # Blob Storage for uploads
├── specs/                          # Spec-driven development artifacts
├── docs/                           # Diagrams and historical notes
├── .github/workflows/              # CI and deployment workflows
└── DEPLOYMENT.md                   # Deployment workflow details

Features

  • Streaming chat: Browser chat UI streams assistant responses from FastAPI over SSE.
  • GitHub App OAuth authentication: On page load the frontend probes GET /auth/session; a 401 triggers a redirect to GET /auth/login, which starts the GitHub App OAuth flow. The returned user access token is encrypted with a Fernet cipher (PBKDF2-derived from the App client secret) and stored as an httponly; secure; samesite=lax session cookie valid for 8 hours.
  • Per-user session namespacing: The decrypted GitHub token is hashed with AES-CMAC (second PBKDF2-derived key) to produce a stable, opaque user namespace. Combined with the optional X-Isolation-Session-ID header, this scopes Copilot SDK sessions per user.
  • Multi-turn sessions: Backend keeps Copilot SDK sessions alive per thread and cleans up idle sessions.
  • Azure AI Foundry BYOK: Alternative model routing via POST /v1/byok/foundry to Azure AI Foundry model deployments; select in the frontend model picker.
  • Tool calling: Backend exposes built-in tools (tools.py) and optional remote MCP server integration (mcp_client.py); configure MCP_SERVER_URL to add external tools. Allowlist/denylist via COPILOT_API_ALLOWED_TOOLS and COPILOT_API_EXCLUDED_TOOLS.
  • File attachments: Frontend uploads supported files to Azure Blob Storage and sends blob references with prompts.
  • Agent teams: Predefined collaboration patterns such as Debate & Critic, Generator & Evaluator, Leadership Discussion, Planner & Executor, and Research & Report.
  • Batch/loop workflows: Fleet runs up to 20 prompts in parallel; Infinite Session chains outputs across iterations.
  • Production container: Multi-stage Docker build serves frontend with nginx, runs FastAPI behind /api/*, and forwards GitHub Copilot CLI telemetry through an OTel Collector sidecar.
  • Azure IaC: Terraform provisions App Service, ACR, Storage, private networking, and monitoring resources.
  • Secure deployment: GitHub Actions uses Azure OIDC; App Service uses managed identity for ACR pull and Azure resource access.

Prerequisites

  • Python 3.12+
  • uv
  • Node.js 20+ and npm 10+
  • GitHub CLI (gh) authenticated with gh auth login
  • Active GitHub Copilot entitlement for local development
  • Terraform 1.5+ for Azure infrastructure
  • Azure CLI for deployment troubleshooting and OIDC setup

Local Development

1. Install backend dependencies

cd app
uv sync --frozen --all-extras

2. Configure local environment

cp .env.example .env

Set COPILOT_APP_CLIENT_ID and COPILOT_APP_CLIENT_SECRET from your GitHub App settings. Configure the GitHub App Callback URL (not the Webhook URL) to point to COPILOT_APP_REDIRECT_URI; for local development use http://localhost:8080/auth/callback. The production value is https://app-agentic-devops.azurewebsites.net/auth/callback.

How session auth works locally: COPILOT_APP_CLIENT_SECRET is used both as the GitHub OAuth client secret and as a PBKDF2 passphrase to derive the Fernet key that encrypts the session cookie. Without it set, GET /auth/login returns HTTP 503 and POST / returns HTTP 401.

3. Start the backend

cd app
uv run agui_server.py

Backend URL: http://127.0.0.1:5100

4. Start the frontend

cd app/frontend
npm ci
npm run dev

Frontend URL: http://localhost:8080

The Vite dev server proxies /api/* to http://127.0.0.1:5100 and strips the /api prefix, matching production nginx behavior.

API Surface

Backend routes are registered without an /api prefix. The prefix is added only by frontend/proxy layers.

MethodPathAuth requiredDescription
POST/Chat SSE stream (GitHub Copilot)
POST/v1/byok/foundryChat SSE stream (Azure AI Foundry BYOK); bypasses OAuth
GET/v1/modelsList Copilot models via the Anthropic-compatible adapter
POST/v1/messagesAnthropic-compatible Messages API adapter backed by Copilot SDK
GET/auth/loginStart GitHub App OAuth sign-in; redirects to GitHub
GET/auth/callbackGitHub App OAuth callback; sets encrypted session cookie
GET/auth/sessionReturns {"authenticated": true} or HTTP 401; used as session probe
POST/auth/logoutDeletes github_oauth_session cookie (HTTP 204)
GET/healthHealth check
POST/v1/files/uploadUpload a validated file to Azure Blob Storage
DELETE/v1/threads/{thread_id}Disconnect and clean up a chat thread
POST/v1/threads/{thread_id}/abortAbort active chat or team generation
POST/v1/fleetStart a parallel prompt batch job
POST/v1/infinite-sessionStart chained reasoning iterations
GET/v1/patternsList available multi-agent team patterns
POST/v1/teams/streamStream multi-agent team execution
GET/v1/jobs/{job_id}Poll async job status
GET/v1/mcp/toolsList tools available from the remote MCP server
GET/docsFastAPI OpenAPI UI

Auth is enforced by direct checks inside route handlers, not via FastAPI Depends. POST / reads and validates the github_oauth_session cookie; /v1/models and /v1/messages are unauthenticated at the route level and are intended to be isolated by network controls; all other routes are currently unauthenticated at the route level.

Environment Variables

VariableRequiredDefaultDescription
COPILOT_APP_CLIENT_IDOAuthunsetGitHub App client ID. Also accepted as GITHUB_CLIENT_ID
COPILOT_APP_CLIENT_SECRETOAuthunsetGitHub App client secret. Dual use: sent to GitHub in the token exchange AND used as PBKDF2 passphrase (600k iterations, SHA-256) to derive both the Fernet cookie-encryption key and the AES-CMAC session-namespace key. Also accepted as GITHUB_CLIENT_SECRET
COPILOT_APP_REDIRECT_URIOAuthunsetGitHub App callback URL. Local dev: http://localhost:8080/auth/callback. Production: https://<app>.azurewebsites.net/auth/callback. Also accepted as GITHUB_OAUTH_REDIRECT_URI
COPILOT_API_HOSTNo0.0.0.0Backend bind host
COPILOT_API_PORTNo5100Backend port
COPILOT_API_LOG_LEVELNoINFOBackend log level
COPILOT_API_SESSION_TIMEOUTNo120.0Idle session timeout in seconds
COPILOT_API_ISOLATION_SESSION_HEADERNoX-Isolation-Session-IDHeader used to scope runtime and file isolation. When OAuth is active, combined with the per-user AES-CMAC namespace to form the final session pool key
COPILOT_API_SESSION_CONFIG_ROOT_DIRNo.copilot-session-configBase directory for per-isolation Copilot session config
COPILOT_API_AZURE_STORAGE_BLOB_ENDPOINTFile uploadunsetBlob endpoint, for example https://<account>.blob.core.windows.net
COPILOT_API_AZURE_STORAGE_CONTAINER_NAMENouploadsUpload container name
COPILOT_API_SKILL_DIRECTORIESNounsetExtra directories (os.pathsep- or comma-separated) scanned for Agent Skills (SKILL.md), in addition to built-in app/skills/
COPILOT_API_DISABLED_SKILLSNounsetComma-separated skill names to disable
APPLICATIONINSIGHTS_CONNECTION_STRINGNounsetEnables Azure Monitor OpenTelemetry export
OTEL_SERVICE_NAMENoagentic-devops-starterOpenTelemetry service name
COPILOT_API_CLI_OTEL_ENDPOINTNoauto in App Service when App Insights is configuredGitHub Copilot CLI OTLP endpoint, typically http://127.0.0.1:4318 for the local Collector companion process
COPILOT_API_CLI_OTEL_CAPTURE_CONTENTNofalseWhether Copilot CLI telemetry captures prompt/response content
COPILOT_API_APP_CONFIG_ENDPOINTNounsetAzure App Configuration endpoint for feature flags (e.g. https://<store>.azconfig.io); loaded at startup with lower precedence than env vars
COPILOT_API_APP_CONFIG_LABELNounsetLabel filter applied when fetching from Azure App Configuration
MCP_SERVER_URLNounsetURL of the remote MCP server (e.g. https://<name>.azurecontainerapps.io); omit to run with built-in tools only
COPILOT_API_TOOL_TIMEOUTNounsetTimeout in seconds for individual tool calls
COPILOT_API_ALLOWED_TOOLSNounsetComma-separated allowlist of tool names; overrides the denylist when set
COPILOT_API_EXCLUDED_TOOLSNounsetComma-separated denylist of tool names to disable
THIRDPARTY_GITHUB_PATThird-party APIunsetToken used to call the GitHub Copilot API directly for GET /v1/models / POST /v1/messages. Use a personal-account-owned fine-grained PAT (github_pat_...) with the Copilot Requests account permission; classic PATs (ghp_...) are unsupported
VITE_AGUI_ENDPOINTNo/apiFrontend API base URL

Development Commands

Backend commands run from app/:

uv sync --frozen --all-extras
uv run agui_server.py
uv run ruff check .
uv run ruff format .
uv run mypy .
uv run pytest tests/ -v

Frontend commands run from app/frontend/:

npm ci
npm run dev
npm run build
npm run lint
npm run type-check
npm run test
npm run test:e2e

Azure Infrastructure

Terraform lives in infra/ and provisions:

  • Resource group
  • Azure Container Registry
  • Linux App Service Plan
  • Linux Web App with system-assigned managed identity
  • Log Analytics Workspace
  • Storage Account and uploads container
  • VNet with App Service integration subnet
  • Blob Storage private endpoint and private DNS zone
cd infra
cp terraform.tfvars.example terraform.tfvars
# Edit globally unique names: acr_name, app_service_name, storage_account_name

terraform init
terraform fmt -check
terraform validate
terraform plan
terraform apply
terraform output

Key outputs used by deployment are acr_name, app_service_name, and resource_group_name.

Deployment

Deployment uses .github/workflows/deploy.yml:

  1. Build the combined frontend/backend image from app/Dockerfile.appservice.
  2. Push both ${{ github.sha }} and latest tags to ACR.
  3. Set secret-based App Service settings.
  4. Deploy the image to Azure App Service.
  5. Verify GET /health.
  6. Run Playwright E2E tests against the deployed URL.

Required GitHub Actions secrets:

SecretDescription
AZURE_CLIENT_IDAzure OIDC app registration client ID
AZURE_TENANT_IDAzure tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
ACR_NAMEAzure Container Registry name
APP_SERVICE_NAMEApp Service name
RESOURCE_GROUPResource group name
COPILOT_APP_CLIENT_IDGitHub App client ID; injected as GITHUB_CLIENT_ID
COPILOT_APP_CLIENT_SECRETGitHub App client secret; injected as GITHUB_CLIENT_SECRET. Used for OAuth token exchange and as PBKDF2 passphrase for cookie encryption
AZURE_AI_PROJECT_ENDPOINTAzure AI Foundry endpoint for BYOK routing
AZURE_AI_MODEL_DEPLOYMENT_NAMEAzure AI Foundry model deployment name
FOUNDRY_AUTH_MODEFoundry auth mode: auto, api_key, or azure_identity
FOUNDRY_API_KEYFoundry API key, required only for api_key mode
FOUNDRY_WIRE_APIFoundry wire API: responses or completions
APP_CONFIG_ENDPOINTAzure App Configuration endpoint; injected as COPILOT_API_APP_CONFIG_ENDPOINT
APP_CONFIG_LABELAzure App Configuration label filter; injected as COPILOT_API_APP_CONFIG_LABEL
MCP_SERVER_URLRemote MCP server URL; injected as MCP_SERVER_URL
APPLICATIONINSIGHTS_CONNECTION_STRINGEnables Azure Monitor telemetry and starts the OTel Collector sidecar
PLAYWRIGHT_GITHUB_TOKENGitHub PAT used by E2E global-setup to forge a valid Fernet session cookie for smoke tests
PLAYWRIGHT_GITHUB_CLIENT_SECRETSame value as COPILOT_APP_CLIENT_SECRET; used by E2E global-setup to replicate the PBKDF2+Fernet cookie cipher in Node.js

See DEPLOYMENT.md and .github/AZURE_SETUP.md.

CI

.github/workflows/ci.yml runs on pushes and pull requests to main and develop:

cd app
uv sync --frozen --all-extras
uv run ruff check .
uv run pytest tests/ -v

Specs

This repository follows spec-driven development. Existing specs:

SpecDescription
001-agent-frameworkInitial agent framework integration
002-ag-ui-integrationAG-UI protocol integration
003-copilotkit-frontendReact/CopilotKit frontend
004-chat-theme-selectorChat theme selector
005-multi-turn-conversationMulti-turn conversation support
006-github-copilot-sdkGitHub Copilot SDK migration
007-agent-team-platformMulti-agent team platform
008-blob-file-uploadBlob-backed file upload support
009-refactor-patterns-yamlRefactor team patterns into YAML-driven configuration
010-tool-calling-integrationBuilt-in and remote MCP tool calling integration

Troubleshooting

Check local backend health:

curl http://127.0.0.1:5100/health

Check deployed health:

curl https://<app-service-name>.azurewebsites.net/health

Tail App Service logs:

az webapp log tail --resource-group <resource-group> --name <app-service-name>

Security Notes

  • GitHub Actions authenticates to Azure with OIDC, not long-lived Azure credentials.
  • App Service uses system-assigned managed identity for ACR pull and optional Azure AI role assignments.
  • Production nginx adds common security headers and proxies only allowed HTTP methods.
  • Blob upload storage is designed for managed identity and private endpoint access.
  • The github_oauth_session cookie is httponly; secure; samesite=lax with an 8-hour TTL. The GitHub user access token is never exposed to JavaScript; it is stored only in the Fernet-encrypted cookie value.
  • COPILOT_APP_CLIENT_SECRET is the PBKDF2 passphrase for both the session cookie cipher and the AES-CMAC session-namespace key. Rotating it invalidates all existing sessions.
  • CSRF protection uses a server-side in-memory state store (_oauth_states); the state token is single-use and expires after 10 minutes. This is single-node only — multi-instance deployments need an external store.
  • Do not commit .env, Terraform state, secrets, or personal tokens.

License

See LICENSE.