AI Prompt: Forma3D.Connect — Nx Cloud DTE Pipeline Optimization¶
Purpose: Instruct an AI to integrate Nx Cloud with Distributed Task Execution (DTE) into the Azure DevOps CI/CD pipeline, collapsing the sequential Validate → Test → Build stages into a single distributed stage for maximum parallelism
Estimated Effort: 8–12 hours
Prerequisites: Current Azure DevOps pipeline working (azure-pipelines.yml), Nx workspace functional
Output: Dramatically faster CI pipeline using Nx Cloud remote caching and Nx Agents for distributed task execution
Status: 🚧 TODO
🎯 Mission¶
Integrate Nx Cloud with Distributed Task Execution (DTE) into the Forma3D.Connect CI/CD pipeline. The current pipeline runs Validate (lint + typecheck), Test, and Build as three sequential stages — each with redundant dependency installations. DTE allows a single orchestrator job to distribute all Nx tasks (lint, typecheck, test, build) across multiple agent machines in parallel, dramatically reducing total pipeline duration.
Why This Matters:
The current pipeline has three critical bottlenecks:
- Sequential stages: Validate must finish before Test starts, Test must finish before Build starts
- Redundant setup: Node.js, pnpm, and
pnpm installrun separately in every job (Lint, TypeCheck, UnitTests, BuildAll) — that's 4× the install overhead - Limited parallelism: Nx runs tasks with
--parallel=3on a single machine, but the workspace has 6+ projects — true distribution across machines would be faster
Phase delivers:
- Nx Cloud workspace connection with remote caching
- Distributed Task Execution using Nx Agents (or manual DTE with self-hosted Azure agents)
- Collapsed Validate + Test + Build into a single distributed stage for feature branches
- Preserved Docker packaging and deployment stages for main branch
- Remote cache hits eliminating redundant work across branches
Critical constraints:
- The Docker packaging jobs (PackageApi, PackageWeb, PackageDocs) and all deployment stages must remain unchanged — DTE only applies to Nx-managed tasks (lint, typecheck, test, build)
- The pipeline must continue working if Nx Cloud is temporarily unavailable (graceful fallback)
- Existing
nx affectedbehavior must be preserved - Azure DevOps variable groups and secrets must not be exposed to Nx Cloud agents
- The
NX_CLOUD_ACCESS_TOKENmust be stored as an Azure DevOps secret variable
📌 Context (Current State)¶
Current Pipeline Structure¶
Validate ──→ Test ──→ Build & Package ──→ Deploy Staging ──→ Acceptance Test ──→ ...
├─ Lint ├─ UnitTests ├─ DetectAffected
└─ TypeCheck ├─ BuildAll
├─ PackageApi
├─ PackageWeb
└─ PackageDocs
Key observations:
- Validate stage: 2 parallel jobs (Lint + TypeCheck), each runs
install-dependencies.ymltemplate separately - Test stage: 1 job (UnitTests) with PostgreSQL service container, runs
install-dependencies.ymlagain - Build stage (main only): DetectAffected → then BuildAll + PackageApi + PackageWeb + PackageDocs in parallel
- Nx parallelism: All Nx commands use
--parallel=3(single machine) - No Nx Cloud:
nx.jsonhas nonxCloudAccessTokenor cloud configuration - No remote cache: Every branch rebuild recalculates everything from scratch
Current nx.json (Relevant Sections)¶
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": ["default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "..."]
},
"plugins": [
{ "plugin": "@nx/webpack/plugin" },
{ "plugin": "@nx/eslint/plugin" },
{ "plugin": "@nx/vite/plugin" },
{ "plugin": "@nx/jest/plugin" }
],
"targetDefaults": {
"@nx/js:tsc": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}
Current install-dependencies.yml Template¶
The template runs in every job and includes:
- Install Node.js 20.x
- Enable corepack + pnpm 9
- Cache pnpm store
pnpm install --frozen-lockfile- Fetch
mainbranch for Nx affected (feature branches only)
Projects in Workspace¶
| Project | Targets |
|---|---|
api |
build, serve, lint, test, typecheck |
web |
build, serve, lint, test, typecheck |
api-client |
lint, test |
acceptance-tests |
lint, test (Playwright) |
api-e2e |
lint, test |
domain-contracts |
lint, test |
ui |
lint, test, build |
utils |
lint, test |
🛠️ Tech Stack Reference¶
- CI: Azure DevOps Pipelines (YAML),
ubuntu-latestpool - Monorepo: Nx (no Nx Cloud yet)
- Package manager: pnpm 9 via corepack
- Node: 20.x
- Database (tests): PostgreSQL 16 via service container
- Docker: Multi-stage builds for API, Web, and Docs images
🏗️ Architecture: Two Implementation Options¶
Option A: Nx Cloud Hosted Agents (Recommended)¶
Uses Nx Cloud's managed infrastructure to run agent machines. Simplest setup, no additional Azure agent configuration needed.
Pros:
- Minimal CI config changes — one
start-ci-runcommand - Nx Cloud optimally distributes tasks based on project graph and historical data
- Remote caching across all branches and PRs
- Built-in retry for flaky tasks
- No need to manage agent pools
Cons:
- Requires Nx Cloud subscription (free tier: 500 CI pipeline executions/month)
- Tasks run on Nx Cloud infrastructure (data leaves Azure)
- API tests needing PostgreSQL require special handling
Option B: Manual DTE with Self-Hosted Azure Agents¶
Uses Azure DevOps parallel jobs as DTE agents. Tasks are still coordinated by Nx Cloud, but run on your own infrastructure.
Pros:
- Full control over agent environment (PostgreSQL available)
- Data stays on Azure infrastructure
- Can use existing Azure DevOps parallel job slots
Cons:
- More complex pipeline configuration
- Need to manage agent setup in each job
- Requires at least 3–4 Azure parallel job slots
Recommendation: Start with Option B (Manual DTE) since the API tests require a PostgreSQL service container. Nx Cloud Hosted Agents can be adopted later once test infrastructure is decoupled.
📁 Files to Create/Modify¶
Modified Files¶
nx.json # Add nxCloudAccessToken
azure-pipelines.yml # Restructure Validate + Test + Build stages
.azuredevops/pipelines/templates/install-dependencies.yml # Minor updates for DTE compatibility
package.json # Add nx-cloud dependency
New Files¶
.azuredevops/pipelines/templates/nx-agent.yml # Reusable template for DTE agent jobs
🔧 Implementation Phases¶
Phase 1: Connect Workspace to Nx Cloud (1 hour)¶
Priority: Critical | Impact: High | Dependencies: None
1. Create Nx Cloud Account & Connect Workspace¶
Run in the workspace root:
pnpm nx connect
This will:
- Create an Nx Cloud workspace
- Add
nxCloudAccessToken(ornxCloudId) tonx.json
2. Verify nx.json Configuration¶
After connecting, nx.json should include:
{
"nxCloudAccessToken": "<your-read-write-token>"
}
Important: For CI, use a read-write token. The token added by nx connect is typically read-write. Store it as an Azure DevOps pipeline secret variable (NX_CLOUD_ACCESS_TOKEN) and remove the plaintext value from nx.json. Instead, the CI pipeline will pass it via environment variable.
3. Update nx.json for CI Token Injection¶
Replace the hardcoded token with a comment indicating it's injected via CI:
{
"nxCloudAccessToken": "<token>"
}
Alternatively, keep the read-only token in nx.json for local developer caching and override with a read-write token in CI via the NX_CLOUD_ACCESS_TOKEN environment variable.
4. Add nx-cloud Dependency¶
pnpm add -D nx-cloud
5. Verify Remote Caching Works Locally¶
pnpm nx build api
# Run again — should show "remote cache hit"
pnpm nx build api
Phase 2: Add Azure DevOps Secret Variables (30 minutes)¶
Priority: Critical | Impact: High | Dependencies: Phase 1
1. Add NX_CLOUD_ACCESS_TOKEN to Pipeline Variables¶
Add to the forma3d-staging variable group (or create a new nx-cloud variable group):
- Variable name:
NX_CLOUD_ACCESS_TOKEN - Value: The read-write access token from Nx Cloud dashboard
- Secret: Yes (mark as secret)
2. Update Pipeline Variables Section¶
Add to azure-pipelines.yml variables:
variables:
- group: forma3d-staging
# Nx Cloud
- name: NX_CLOUD_ACCESS_TOKEN
value: $(NX_CLOUD_ACCESS_TOKEN) # From variable group
Or pass via environment variable on each Nx command step.
Phase 3: Create DTE Agent Template (1 hour)¶
Priority: High | Impact: High | Dependencies: Phase 2
1. Create .azuredevops/pipelines/templates/nx-agent.yml¶
# ============================================================================
# Template: Nx Cloud DTE Agent
# ============================================================================
# Reusable template for Nx Cloud Distributed Task Execution agent jobs.
# Each agent connects to Nx Cloud and waits for task assignments.
# ============================================================================
steps:
- checkout: self
fetchDepth: 0
fetchFilter: tree:0
persistCredentials: true
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- task: Cache@2
displayName: 'Cache pnpm store'
inputs:
key: 'pnpm | "$(Agent.OS)" | pnpm-lock.yaml'
restoreKeys: |
pnpm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.pnpm-store
- script: |
pnpm config set store-dir $(Pipeline.Workspace)/.pnpm-store
pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: npx nx-cloud start-agent
displayName: 'Start Nx Cloud Agent'
env:
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
Phase 4: Restructure Feature Branch Pipeline (3 hours)¶
Priority: High | Impact: Very High | Dependencies: Phase 3
This is the core change. For feature branches (non-main), collapse Validate + Test into a single stage with DTE.
Current Flow (Feature Branches)¶
Validate Stage Test Stage
├─ Lint (affected) └─ UnitTests (affected)
└─ TypeCheck
↓ sequential ↓ ↓ sequential ↓
New Flow (Feature Branches with DTE)¶
CI Stage (single stage)
├─ Main Job (orchestrator) ← issues nx affected -t lint,typecheck,test
├─ Agent 1 ← receives tasks from Nx Cloud
├─ Agent 2 ← receives tasks from Nx Cloud
└─ Agent 3 ← receives tasks from Nx Cloud
Updated azure-pipelines.yml — Feature Branch Stages¶
Replace the separate Validate and Test stages with a single CI stage for feature branches:
stages:
# --------------------------------------------------------------------------
# Stage: CI (Feature Branches) — Distributed with Nx Cloud
# --------------------------------------------------------------------------
# Uses Nx Cloud DTE to distribute lint, typecheck, and test tasks
# across multiple agents for maximum parallelism.
# Only runs on non-main branches.
# --------------------------------------------------------------------------
- stage: CI
displayName: 'CI (Distributed)'
condition: and(
eq('${{ parameters.loadTestOnly }}', 'false'),
ne(variables.isMain, true)
)
jobs:
# DTE Agent jobs — these wait for task assignments from Nx Cloud
- job: Agent
displayName: 'Nx Cloud Agent'
strategy:
parallel: 3 # Number of agents — adjust based on workload
pool:
vmImage: 'ubuntu-latest'
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: forma3d_connect_test
steps:
- template: .azuredevops/pipelines/templates/nx-agent.yml
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
# Main orchestrator job — issues Nx commands, Nx Cloud distributes them
- job: Main
displayName: 'Nx Cloud Orchestrator'
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
fetchDepth: 0
fetchFilter: tree:0
persistCredentials: true
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
# Start DTE: distribute tasks to manual agents, stop after test target
- script: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="test"
displayName: 'Start Nx Cloud DTE'
env:
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
# Run all targets in one command — Nx Cloud distributes across agents
- script: pnpm nx affected -t lint,typecheck,test --parallel=2 --exclude=api-e2e,acceptance-tests
displayName: 'Run Affected Tasks (Distributed)'
env:
CI: 'true'
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
failTaskOnMissingResultsFile: false
Keep Main Branch Pipeline Intact¶
For the main branch, keep the existing Validate → Test → Build flow but add Nx Cloud remote caching for speed. The main branch needs Docker packaging which doesn't benefit from DTE.
Update the existing Validate and Test stages to include:
# Add to each Nx command step:
env:
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
This gives main branch runs free remote caching — if a feature branch already validated the same code, main will get cache hits.
Phase 5: Main Branch Build Stage with DTE (2 hours)¶
Priority: Medium | Impact: High | Dependencies: Phase 4
Optionally, also distribute the main branch Validate + Test stages. The Build stage's BuildAll job can also benefit from DTE, but the Docker packaging jobs (PackageApi, PackageWeb, PackageDocs) must remain as separate Azure jobs since they use Docker buildx directly.
Updated Main Branch Flow¶
# --------------------------------------------------------------------------
# Stage: Validate & Test (Main Branch) — Distributed with Nx Cloud
# --------------------------------------------------------------------------
- stage: ValidateAndTest
displayName: 'Validate & Test'
condition: and(
eq('${{ parameters.loadTestOnly }}', 'false'),
eq(variables.isMain, true)
)
jobs:
- job: Agent
displayName: 'Nx Cloud Agent'
strategy:
parallel: 3
pool:
vmImage: 'ubuntu-latest'
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: forma3d_connect_test
steps:
- template: .azuredevops/pipelines/templates/nx-agent.yml
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
- job: Main
displayName: 'Nx Cloud Orchestrator'
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
fetchDepth: 0
fetchFilter: tree:0
persistCredentials: true
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="build"
displayName: 'Start Nx Cloud DTE'
env:
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
# Run ALL targets including build — Nx Cloud distributes across agents
- script: pnpm nx run-many -t lint,typecheck,test,build --all --parallel=2 --exclude=api-e2e,acceptance-tests
displayName: 'Run All Tasks (Distributed)'
env:
CI: 'true'
NX_CLOUD_ACCESS_TOKEN: $(NX_CLOUD_ACCESS_TOKEN)
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
failTaskOnMissingResultsFile: false
# Build & Package stage now only handles Docker packaging
- stage: Build
displayName: 'Package Docker Images'
dependsOn: ValidateAndTest
condition: and(succeeded(), eq(variables.isMain, true), eq('${{ parameters.loadTestOnly }}', 'false'))
jobs:
- job: DetectAffected
# ... (keep existing DetectAffected job unchanged)
- job: PackageApi
# ... (keep existing PackageApi job unchanged)
- job: PackageWeb
# ... (keep existing PackageWeb job unchanged)
- job: PackageDocs
# ... (keep existing PackageDocs job unchanged)
Phase 6: Graceful Fallback Without Nx Cloud (1 hour)¶
Priority: Medium | Impact: Medium | Dependencies: Phase 4
If Nx Cloud is unavailable, the pipeline should not fail. Add a fallback mechanism.
1. Use --fallback-on-error Flag¶
The start-ci-run command supports a fallback mode:
- script: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="test" || echo "Nx Cloud unavailable, running locally"
displayName: 'Start Nx Cloud DTE (with fallback)'
2. Environment Variable Fallback¶
Add a pipeline parameter for disabling DTE:
parameters:
- name: disableDTE
displayName: 'Disable Distributed Task Execution'
type: boolean
default: false
Use a condition to skip DTE when disabled:
- script: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="test"
displayName: 'Start Nx Cloud DTE'
condition: eq('${{ parameters.disableDTE }}', 'false')
Phase 7: Verify and Tune (2 hours)¶
Priority: High | Impact: High | Dependencies: All previous phases
1. Verify Remote Caching¶
Run a feature branch pipeline twice — the second run should show cache hits for unchanged projects:
> NX Successfully ran target lint for 6 projects (cache hit for 6)
> NX Successfully ran target test for 5 projects (cache hit for 5)
2. Verify DTE Distribution¶
Check the Nx Cloud dashboard to confirm tasks are distributed across agents:
- Navigate to your Nx Cloud dashboard
- Find the CI run
- Verify tasks are spread across Agent 1, Agent 2, Agent 3
- Check for idle time — agents should be kept busy
3. Tune Agent Count¶
Start with 3 agents and adjust based on:
| Workspace size | Recommended agents |
|---|---|
| < 5 projects | 2 agents |
| 5–15 projects | 3 agents |
| 15–30 projects | 4–5 agents |
| 30+ projects | 5–8 agents |
Since the workspace currently has ~8 projects, 3 agents is a good starting point.
4. Monitor Azure DevOps Parallel Job Consumption¶
Each DTE agent uses one Azure DevOps parallel job slot. With 3 agents + 1 orchestrator = 4 parallel job slots needed. Verify your Azure DevOps organization has enough slots.
📊 Expected Performance Improvements¶
Feature Branch (lint + typecheck + test)¶
| Metric | Before (Sequential) | After (DTE, 3 agents) |
|---|---|---|
| Install deps overhead | 3× (Lint + TypeCheck + UnitTests) | 4× but in parallel |
| Lint | ~2 min | ~40s (distributed) |
| TypeCheck | ~2 min | ~40s (distributed) |
| Test | ~3 min | ~1 min (distributed) |
| Total wall time | ~9–12 min | ~3–5 min |
| Speedup | — | ~2.5–3× |
Main Branch (lint + typecheck + test + build + package)¶
| Metric | Before (Sequential) | After (DTE + Docker parallel) |
|---|---|---|
| Validate + Test + Build | ~15–20 min | ~5–8 min (DTE) |
| Docker packaging | ~5 min (parallel) | ~5 min (unchanged) |
| Total before deploy | ~20–25 min | ~10–13 min |
| Speedup | — | ~2× |
Cross-Branch Caching Bonus¶
When a feature branch is merged to main, the main branch pipeline will get remote cache hits for all tasks that were already validated on the feature branch. This can further reduce main branch pipeline time to near-zero for the validate/test/build steps.
✅ Validation Checklist¶
Setup¶
- Nx Cloud workspace created and connected
-
nx.jsonupdated with cloud configuration -
nx-cloudpackage added to devDependencies -
NX_CLOUD_ACCESS_TOKENstored as Azure DevOps secret variable - Remote caching verified locally (
nx build apishows cache hits on second run)
Pipeline — Feature Branches¶
- Single CI stage replaces Validate + Test stages
- 3 DTE agent jobs start and connect to Nx Cloud
- Orchestrator job runs
nx affected -t lint,typecheck,test - Tasks are distributed across agents (verify in Nx Cloud dashboard)
- PostgreSQL service container available on agent jobs for API tests
- Test results still published to Azure DevOps
- Pipeline completes faster than before
Pipeline — Main Branch¶
- ValidateAndTest stage uses DTE for lint, typecheck, test, build
- Docker packaging stage (PackageApi, PackageWeb, PackageDocs) unchanged
- DetectAffected still outputs correct variables for deployment stages
- Deployment stages (DeployStaging, AcceptanceTest, etc.) unaffected
- Remote cache hits visible when feature branch already validated the code
Fallback¶
- Pipeline parameter
disableDTEworks correctly - Pipeline doesn't fail if Nx Cloud is temporarily unavailable
- Agent jobs exit gracefully when orchestrator finishes
Verification Commands¶
# Build passes
pnpm nx build api
pnpm nx build web
# Tests pass
pnpm nx run-many -t test --all --exclude=api-e2e,acceptance-tests
# Lint passes
pnpm nx run-many -t lint --all
# Remote caching works (second run should be instant)
pnpm nx run-many -t build --all
pnpm nx run-many -t build --all # Should show cache hits
# Verify Nx Cloud connection
pnpm nx-cloud verify
🚫 Constraints and Rules¶
MUST DO¶
- Store
NX_CLOUD_ACCESS_TOKENas a secret — never commit to repository - Keep Docker packaging jobs as standalone Azure jobs (not DTE-managed)
- Keep all deployment stages completely unchanged
- Provide PostgreSQL on every DTE agent that might run test tasks
- Preserve test result publishing to Azure DevOps
- Add
disableDTEparameter as a safety escape hatch - Run
prisma generateon all agents (needed for API lint/test/build)
MUST NOT¶
- Expose staging/production secrets to Nx Cloud agents
- Remove the existing pipeline structure for deployment stages
- Use Nx Cloud Hosted Agents for tasks requiring PostgreSQL (use manual DTE)
- Hard-code the Nx Cloud access token in
nx.jsonor pipeline YAML - Skip the fallback mechanism — CI must work even if Nx Cloud is down
- Change the Docker packaging strategy (multi-stage builds are already optimized)
🔄 Migration Strategy¶
To avoid disruption, implement in stages:
- Week 1: Connect Nx Cloud + enable remote caching only (no DTE). All existing pipeline stages remain. Verify cache hits across branches.
- Week 2: Add DTE for feature branches only. Main branch keeps existing stages but gains remote cache. Monitor for issues.
- Week 3: Add DTE for main branch Validate + Test. Docker packaging remains as-is. Tune agent count based on observed performance.
This approach allows rolling back at any stage if issues arise.
END OF PROMPT
This prompt integrates Nx Cloud with Distributed Task Execution into the Forma3D.Connect Azure DevOps pipeline. The AI should connect the workspace to Nx Cloud, configure manual DTE with Azure agent jobs, restructure the pipeline stages for maximum parallelism, and preserve all Docker packaging and deployment functionality. Remote caching and distributed execution should reduce feature branch CI time by ~2.5–3× and main branch CI time by ~2×.