Agentic CI/CD with Azure Cosmos DB: Isolated Envs for a Swarm of AI Agents A Microsoft DevBlogs post demonstrates giving each AI coding task its own disposable Azure Cosmos DB emulator environment so that parallel agents editing the FleetCharge sample application cannot collide on shared state. The setup, built on the FleetCharge sample repository, runs two environments that deliberately reuse identical document identities — database `fleetchargetest-agent-isolation`, container `dispatchUpdates`, document ID `current-dispatch`, partition key `SEA-01`, and starting pickup bay `BAY-2` — while changing them independently and cleaning up only their own containers. The post tests GitHub Copilot as the coding agent editing FleetCharge in GitHub Actions, with Ollama used separately for local application testing and not to run or evaluate the Copilot agents. Give each coding task its own disposable database environment, then merge the reviewed changes – not the test database. When parallel coding agents share a database In From Local Development to CI Workflows with the Azure Cosmos DB Emulator https://devblogs.microsoft.com/cosmosdb/from-local-development-to-ci-workflows-with-the-azure-cosmos-db-emulator/ , we used FleetCharge to make local development and CI repeatable. Start the emulator, seed known data, run the checks, and clean up afterward. Now consider several AI coding agents working on the same application at once. One is changing dispatch behavior. Another is adding a depot rule. A third is updating tests. Each has its own branch, and perhaps its own Git worktree, but they all connect to the same development database. An agent resets the fixtures while another agent is checking a write. A test changes a document that a different task expects to remain unchanged. One cleanup script deletes the database while another test suite is still running. The code is isolated. The state is not. Azure Cosmos DB does not require the same table-schema migration workflow as a relational database, but that does not remove shared-state collisions. Applications still depend on document shapes, partition keys, indexing policies, and predictable test data. The next step is to give each coding task an isolated data environment: Separate worktree. Separate emulator. Known fixtures. Independent tests. Owned cleanup. This article tests GitHub Copilot as a coding agent: Copilot edits FleetCharge in GitHub Actions, with a separate Azure Cosmos DB emulator for each task. Ollama was used separately for local application testing. It is not used to run or evaluate the Copilot coding agents in this workflow. The database equivalent of a Git worktree A Git worktree lets me work on separate branches in separate directories. A change in one working directory does not overwrite the files in another. The equivalent development experience for a database is an environment that a task can change, reset, and discard without disturbing its neighbors. The useful idea is to give every coding task its own reproducible data environment. Each environment starts from the same versioned fixtures, can change independently, and can be discarded without affecting other tasks. The unit of isolation is the task, not just the code branch. For this Azure Cosmos DB example, I am borrowing the isolation model – not claiming the same branching mechanism. The sample starts separate emulator containers and rebuilds their data from versioned synthetic fixtures. It does not create copy-on-write snapshots, clone a production account, or merge database contents. That distinction matters. The goal is to make each task’s starting state reproducible and its changes independent. We can establish that without treating a disposable database as something that must eventually be merged back. Start with two environments, not a swarm Before connecting an orchestrator to several coding agents, I want a smaller question answered: can two environments use the same document identities, change them independently, and clean up without affecting each other? The implementation extends the FleetCharge sample repository https://github.com/AzureCosmosDB/fleetcharge-agent-cosmos-ci . It reuses the existing emulator lifecycle helper, which creates invocation-owned containers, allocates loopback ports, waits for readiness, captures diagnostics, and removes only the container it owns. Both environments deliberately use the same identities: | Setting | Environment A | Environment B | |---|---|---| | Database | fleetchargetest-agent-isolation | fleetchargetest-agent-isolation | | Container | dispatchUpdates | dispatchUpdates | | Document ID | current-dispatch | current-dispatch | | Partition-key value | SEA-01 | SEA-01 | | Starting pickup bay | BAY-2 | BAY-2 | | Updated pickup bay | BAY-7 | BAY-8 | Different document IDs would make it easier to avoid a collision accidentally. Reusing the same database, item ID, and partition-key value makes the intended boundary explicit: these writes must remain separate because they reach different emulator instances. This is a component proof using the real emulator and SDK. The A/B labels represent development environments; this command does not invoke Copilot or Ollama. Spin up isolated environments with known data For this check, I need Python 3.12, uv, and Docker running Linux containers on a supported Windows or Linux x86-64 host. There must be enough Docker memory and CPU for two emulator instances. This emulator-only check does not need a coding agent or an application model. From the source checkout, run: uv run --locked --extra test python -m scripts.agent isolation The command owns the complete lifecycle. It starts two emulator containers, waits for readiness, seeds the existing FleetCharge fixtures, runs the isolation checks, and removes its resources. The emulator’s internal database and readiness ports remain 8081 and 8080 . Docker assigns different host ports for each instance, bound to 127.0.0.1 . The command prints the actual endpoints; it does not require two agents to compete for host port 8081 . Each SDK client receives explicit settings for its owned environment. The proof does not rewrite a shared .env file or mutate process-global environment variables. When this pattern is extended into a coding-agent launcher, the same rule should apply: pass each child process its own configuration rather than changing configuration underneath another task. Startup in this check is sequential. Once both instances are ready, fixture seeding and the competing writes run concurrently. That proves concurrent data isolation without claiming parallel startup has been optimized. A separate database per task inside one emulator can also be a useful, lower-overhead design. Here I am using separate containers so that instance lifetime is independent too: removing A must not stop B. Verify stored state, not just successful requests Both environments begin with Seattle’s dispatch snapshot at BAY-2 . Environment A writes BAY-7 with update reference agent-a-dispatch . Environment B writes BAY-8 with reference agent-b-dispatch . The important check is an independent point read after both writes complete. At the center of that check is the same item-and-partition-key operation used elsewhere in FleetCharge: stored = await container.read item "current-dispatch", partition key="SEA-01", The proof verifies the stored item ID, depot, pickup bay, and update reference. It also reads the PDX-02 snapshot in each environment and checks that its original BAY-99 value and dispatch-099 reference are unchanged. Then it tests the lifecycle boundary. Environment A is removed, its absence is verified, and a new SDK client reads environment B again. B must still contain its own update before it, too, is removed. Every successful execution must pass these checks: | Check | Result | |---|---| | A reads its own Seattle update | BAY-7 / agent-a-dispatch | | B reads its own Seattle update | BAY-8 / agent-b-dispatch | | Portland remains unchanged in both | Passed | | B remains readable after A is removed | Passed | | Both owned containers are gone after cleanup | Passed | A failed read-back assertion exits nonzero, cleans up the owned containers, and does not produce a successful result report. A failed assertion must not look like a successful environment run. Each invocation keeps separate environment logs and a JSON result report under a unique ignored artifacts directory. The result report is written only after successful verification and cleanup. Run isolated task checks in GitHub Actions Local isolation is only half the workflow. When a coding agent opens or updates a pull request, GitHub Actions should rebuild the task’s environment from that revision, run its checks, and preserve the results. It should not reuse a database that another task has already changed. The sample adds a separate Agent task CI workflow in .github/workflows/agentic-ci.yml . Its matrix has two task presets: dispatch runs the dispatch unit and feed integration tests; fleet-rules runs the fleet-rule unit and delivery-wave tool integration tests. Each matrix entry receives its own GitHub-hosted Ubuntu runner and starts one owned emulator with dynamically assigned loopback ports. The task runner is also a local command, so an agent can run the same check before submitting its changes: uv run --locked --extra test python -m scripts.agent ci --task dispatch uv run --locked --extra test python -m scripts.agent ci --task fleet-rules The runner passes the emulator endpoint, readiness URL, and public test key directly to its test subprocess. The existing fixtures seed known data into an isolated database for each test and clean it up afterward. JUnit, emulator logs, and the final result report go into a unique task-specific artifacts directory. The runner reports success only after the tests pass and container removal is verified. Here is the matrix portion of the implemented workflow: name: Agent task CI on: pull request: types: opened, synchronize, reopened workflow dispatch: permissions: contents: read concurrency: group: agent-task-ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: component-tasks: name: Isolated task / ${{ matrix.task }} runs-on: ubuntu-24.04 timeout-minutes: 15 strategy: fail-fast: false matrix: task: dispatch, fleet-rules env: UV PYTHON DOWNLOADS: never PIP DISABLE PIP VERSION CHECK: "1" steps: - name: Check out the tested revision uses: actions/checkout@v7 with: persist-credentials: false - name: Set up Python 3.12 uses: actions/setup-python@v7 with: python-version: "3.12" - name: Install uv 0.13.0 from Microsoft shell: bash run: | python -m pip install --no-deps \ "https://pkgs.dev.azure.com/azure-sdk/public/ packaging/azure-sdk-for-python/pypi/download/uv/0.13/uv-0.13.0-py3-none-manylinux 2 17 x86 64.manylinux2014 x86 64.whl" - name: Run the task against its own emulator run: uv run --locked --extra test python -m scripts.agent ci --task ${{ matrix.task }} - name: Preserve task results and diagnostics if: always uses: actions/upload-artifact@v7 with: name: agent-task-${{ matrix.task }}-${{ github.run attempt }} path: .artifacts/agent-ci/ include-hidden-files: true if-no-files-found: warn retention-days: 7 fail-fast: false lets the other task finish and retain its evidence if one task fails. Uploading artifacts with if: always keeps diagnostics available after a failed check. Each artifact name includes the task and run attempt, so parallel jobs do not overwrite one another. The same workflow has a separate isolation-proof job that runs python -m scripts.agent isolation . That job checks the two-container boundary directly: conflicting writes remain independent, removing A leaves B readable, and both containers are removed at the end. These task checks extend, rather than replace, the application’s existing tests. The original Cosmos emulator CI workflow remains separate from Agent task CI . GitHub Copilot performs the coding tasks in the swarm job; Ollama is not used by that job. A narrow dispatch or fleet-rule check is useful feedback for an individual coding task, not permission to skip verification of the integrated application. Require the relevant checks before merging. The matrix runs submitted tests, not AI coding agents. To check the parallel workflow itself, the sample has a separate swarm check, described below. Attach the environment to a coding task The next layer is to connect this database lifecycle to the coding-agent lifecycle. A branch alone is not enough if agents still share a working directory, configuration file, or test-output path. For each task, the coordinator should allocate: - A dedicated Git worktree and feature branch. - An owned emulator environment with its actual endpoint. - Versioned synthetic fixtures matching that code revision. - Separate logs and test artifacts. - Bounded execution and a cleanup path for success and handled failure. The coding agent makes its changes and runs the appropriate checks inside that task’s environment. Database isolation does not replace application verification: if a change affects FleetCharge’s runtime behavior, run the separate application and inference tests too. Testing the application is a different activity from asking Copilot to modify its code; a direct SDK check is not a substitute for an inference test. The complete workflow looks like this. The sample implements the database lifecycle, the CI checks, and a swarm check with either deterministic stand-ins or real coding agents; a production coding-agent coordinator remains separate: The disposable environment does not need to stay alive while a pull request waits for review. Preserve the evidence and the reproducible inputs, then release the resources. Test the whole workflow, not just its parts A boundary proof and a task runner do not show that parallel tasks behave correctly together. The sample includes a check that runs the workflow in the diagram: uv run --locked --extra test python -m scripts.agent swarm It creates three Git worktrees from the same revision and starts one owned emulator for each. The tasks run concurrently: two use the dispatch area and one uses fleet-rules . Each task receives its own endpoint, changes a file in its worktree, writes a dispatch update, and commits. The check then reads each environment back and fails if the stored update is not that task’s own, and runs the task’s tests against its own emulator. The negative control deliberately runs two deterministic stand-ins against a single shared emulator and requires that one of their writes is lost. This control still uses stand-ins when the isolated tasks use real agents; it tests the shared-state collision, not model behavior. Finally, the runner merges the task branches into an integration worktree, runs the combined tests against a fresh emulator, and removes its owned containers, worktrees, and temporary branches. Logs, patches, and test results remain as evidence. By default the coding step is a deterministic stand-in and does not invoke Copilot. To run actual coding agents for the isolated tasks, pass the GitHub Copilot CLI command: uv run --locked --extra test python -m scripts.agent swarm --agent-command 'copilot -p "{prompt}" -s --allow-all-tools --no-ask-user --disable-builtin-mcps' The runner supplies each task’s own COSMOS settings and a SWARM PYTHON interpreter with the SDK and test tools. Each real agent must make and commit a source change, record its own dispatch state, and pass the task’s checks. Database isolation does not prevent code conflicts. When real agents conflict, the runner asks the same configured command to resolve the merge in the integration worktree. It requires a completed merge retaining both parent commits, no remaining conflict markers, and passing combined tests. Unresolved conflicts, failed tests, and failed agent execution fail the check. The saved per-agent and integration patches still need review; passing tests cannot prove that every semantic intention was preserved. The same agentic-ci.yml includes a manual Real Copilot worktree swarm job. Start Agent task CI on main with run real agents enabled: gh workflow run agentic-ci.yml --repo AzureCosmosDB/fleetcharge-agent-cosmos-ci --ref main -f run real agents=true This uses Copilot CLI on the Actions runner with the native GITHUB TOKEN and job-scoped contents: read and copilot-requests: write permissions, not a stored PAT. An organization owner must enable Allow use of Copilot CLI billed to the organization ; usage is billed to the organization. The job runs only on the default branch, has a 60-minute timeout, and caps each Copilot session at 30 AI credits, the minimum accepted by the tested CLI version. Three tasks and up to two conflict-resolution sessions have a configured ceiling of 150 credits, not a claim about actual consumption. Missing policy, budget, or model access fails the real job rather than substituting stand-ins. In the completed native Actions run https://github.com/AzureCosmosDB/fleetcharge-agent-cosmos-ci/actions/runs/40539601322 , all five jobs passed, including the three genuine Copilot coding tasks. The task checks passed 18, 18, and 57 tests respectively; the dispatch suites overlap, so those counts are not additive. The integrated revision passed 75 combined tests against a fresh emulator. The swarm runner took 140.802 seconds , about two minutes and 21 seconds, excluding job setup. These are observed results, not fixed test counts or guaranteed completion times. The two dispatch agents independently improved the depot-mismatch error message; the fleet-rules agent added a charge-efficiency comment. Their overlapping edits caused a genuine conflict in app/dispatch.py . Copilot resolved it, the runner verified both merge parents, and the combined checks passed. Independent reads returned BAY-7 , BAY-8 , and BAY-9 from the three owned environments while Portland’s snapshot stayed unchanged. Containers, temporary worktrees, and branches were removed; patches, logs, and the 75-test JUnit result remain in the run’s uploaded artifacts. The manual stand-in job remains the default, and ordinary pull requests do not launch these coding agents. The real job disables built-in MCP servers and custom instructions and strips GITHUB TOKEN from Copilot’s shell/MCP tool environments. That is not a security sandbox: agents still have broad runner and Docker access. Use trusted, reviewed code. Generated changes remain disposable local commits and uploaded patches; the job does not push them or merge a pull request. Merge the changes – not the emulator data An agent’s successful test run does not mean its local documents should be copied into a shared environment. Those documents are test state. What belongs in Git is the application change and the instructions needed to reproduce or apply its data-contract changes. For Azure Cosmos DB, that may include a new document property, an updated fixture, a document-version transition, or a container indexing-policy definition. These changes deserve review even though they are not all expressed as relational DDL. A versioned migration should make its intent and assumptions visible: which document version it accepts, what it changes, what it leaves alone, and how it verifies the result. It should also define what happens if execution stops partway through or is repeated. Where concurrent updates are possible, conditional writes using ETags can help detect stale assumptions instead of silently overwriting newer values. A partition-key change needs a separate data-movement plan; it is not just another document-property rename. Indexing-policy changes also need coordination because their effects extend beyond one agent’s test document. Shell scripting can provide the repeatable entry point for an approved migration: select the intended environment explicitly, check prerequisites, run the versioned operation, and stop on failure. The sample does not implement that migration runner or a live-account deployment path. Keep fixture reset scripts separate from deployment migrations. Rebuilding synthetic test data is useful in a disposable environment; it is not an acceptable way to update a live application database. Resolve semantic conflicts before merging Two agents can produce changes that Git merges cleanly but the application cannot use together. One agent might make a new field required while another changes the fixture generator without adding it. Two migrations might touch the same property in different files. Separate indexing-policy edits might each look reasonable but conflict when combined. Isolation prevents their development writes from interfering. It does not resolve those design conflicts. Before merging, review overlapping document conventions and container policies, establish migration ordering where needed, and rebuild a fresh environment from the integrated revision. Run the tests against the combined code, fixtures, and migration sequence – not only against each agent’s branch separately. That integrated check is where the original emulator CI pattern becomes especially useful. Each task gets fast, independent feedback; the pull-request workflow checks whether the combined result still meets the application’s contracts. What this does – and does not – prove The checks establish a useful foundation: two independently owned environments can use identical data identities, run competing writes, and be discarded without losing each other’s state. The swarm check extends that to three concurrent worktrees, shows the same writes colliding on a shared emulator, and shows the branches merging and passing together. The default stand-in check touches separate files, so its merge has no conflicts. The real-agent runs add evidence that Copilot can make code changes, work against separate databases, and complete an integration, including one resolved Git conflict. They do not establish reliable semantic conflict resolution, arbitrary swarm scale, or production readiness. Separate emulator containers also do not create a security sandbox for untrusted coding-agent code, particularly when that code has access to Docker. The vNext emulator documentation https://learn.microsoft.com/azure/cosmos-db/emulator-linux describes its supported feature subset and notes that it does not enforce authentication. Keep its published ports on loopback in a trusted development environment. Emulator tests do not validate live-account identity, authorization, throughput, or production performance. Continuous delivery remains a separate gate: reviewed migration behavior, deployment credentials, environment approvals, and live-service validation still need to be designed and tested. Giving a coding agent a disposable database is not permission to deploy its changes automatically. Give the next coding task its own database Parallel coding agents make it easier to change several parts of an application at once. They also make shared test state a more immediate source of failures. Disposable environments turn that state into an explicit input for each task rather than an accidental dependency on whatever another task last wrote. Start with two environments and prove the boundary. Seed the same identities, make different changes, verify the stored results, and remove one environment while the other is still needed. Then use the task runner locally and in GitHub Actions, run the swarm check, and attach that lifecycle to your own coding agents and reviewed data changes. Use the original emulator CI walkthrough https://devblogs.microsoft.com/cosmosdb/from-local-development-to-ci-workflows-with-the-azure-cosmos-db-emulator/ as the baseline, the FleetCharge repository https://github.com/AzureCosmosDB/fleetcharge-agent-cosmos-ci for the sample, and the Azure Cosmos DB Agent Kit https://github.com/AzureCosmosDB/cosmosdb-agent-kit for Azure Cosmos DB guidance packaged for AI coding agents. Give each agent a reproducible place to work. Merge the reviewed changes. Discard the test state. Documentation About Azure Cosmos DB Azure Cosmos DB is a fully managed NoSQL and vector database for modern applications, including AI. Ready to move beyond local testing? Try Azure Cosmos DB for free https://cosmos.azure.com/try/ . Follow us on X https://twitter.com/AzureCosmosDB , YouTube https://aka.ms/AzureCosmosDBYouTube and LinkedIn https://www.linkedin.com/company/azure-cosmos-db/ for product updates and developer walkthroughs.