
Agentic CI/CD with Azure Cosmos DB: Isolated Envs for a Swarm of AI Agents
Give each coding task its own disposable database environment, then merge the reviewed changes – not the test database.
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
Give each coding task its own disposable database environment, then merge the reviewed changes – not the test database.
In 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.
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.
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.
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. 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:
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.
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.
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.
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:
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.
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.
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:
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:
sequenceDiagram participant C as Coordinator participant A as Coding agent participant E as Owned emulator participant R as Pull request and CI
C->>A: Assign task and dedicated Git worktree A->>E: Start instance and wait for readiness A->>E: Seed versioned fixtures A->>E: Run tests and verify stored state E-->>A: Results and diagnostics A->>R: Submit code, fixtures, and migration changes A->>E: Tear down owned environment R->>R: Review changes an
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。