
코딩 작업마다 폐기 가능한 Azure Cosmos DB 환경을 따로 제공하면 여러 AI 에이전트가 같은 테스트 데이터를 바꿔 생기는 충돌을 막을 수 있다. FleetCharge 예제는 에뮬레이터를 작업별로 실행하고, 검토한 코드 변경만 병합하는 방법을 보여준다.
Azure Cosmos DB 에뮬레이터로 로컬 개발에서 CI 워크플로까지에서는 에뮬레이터를 시작하고 알려진 데이터를 넣어 테스트한 뒤 정리하는 방식으로 FleetCharge의 로컬 개발과 지속적 통합(CI)을 반복 가능하게 만들었다. 여러 코딩 에이전트가 동시에 작업하면 코드 브랜치는 나뉘어 있어도 개발 데이터베이스는 공유할 수 있다. 한 에이전트가 테스트 데이터를 초기화하는 동안 다른 에이전트가 데이터를 읽거나 쓰면 작업이 서로 영향을 준다.
Azure Cosmos DB는 관계형 데이터베이스와 같은 테이블 스키마 마이그레이션 절차를 요구하지 않지만, 공유 상태 충돌까지 없어지는 것은 아니다. 애플리케이션은 문서 구조, 파티션 키, 인덱싱 정책, 예측 가능한 테스트 데이터에 여전히 의존한다. 이 예제는 작업마다 별도의 에뮬레이터를 만들고 버전 관리되는 합성 데이터로 초기화한다. 프로덕션 계정을 복제하거나 데이터베이스 스냅샷을 만들고 내용을 병합하는 방식은 아니다.
작업별 에뮬레이터로 상태 분리하기
예제는 FleetCharge 저장소의 기존 에뮬레이터 수명 관리 도구를 확장한다. 이 도구는 실행이 소유한 컨테이너를 만들고, 로컬 루프백 포트를 할당하며, 준비 상태를 확인하고, 진단 정보를 저장한 뒤 자신이 만든 컨테이너만 제거한다.
두 환경은 일부러 같은 데이터베이스와 문서 식별자를 사용한다. 따라서 서로 다른 결과가 나오는 이유는 식별자가 달라서가 아니라 각 환경이 별도의 에뮬레이터 인스턴스이기 때문이다.
| 설정 | 환경 A | 환경 B |
|---|---|---|
| 데이터베이스 | fleetchargetest-agent-isolation | fleetchargetest-agent-isolation |
| 컨테이너 | dispatchUpdates | dispatchUpdates |
| 문서 ID | current-dispatch | current-dispatch |
| 파티션 키 값 | SEA-01 | SEA-01 |
| 시작 픽업 구역 | BAY-2 | BAY-2 |
| 변경 픽업 구역 | BAY-7 | BAY-8 |
이 검증에는 Python 3.12, uv, 그리고 지원되는 Windows 또는 Linux x86-64 호스트에서 Linux 컨테이너를 실행하는 Docker가 필요하다. 에뮬레이터 두 개를 실행할 충분한 Docker 메모리와 CPU도 필요하다. 코딩 에이전트나 애플리케이션 모델은 사용하지 않는다. 소스 체크아웃에서 다음 명령을 실행한다.
uv run --locked --extra test python -m scripts.agent_isolation
명령은 에뮬레이터 두 개를 시작하고 준비 상태를 확인한 뒤 기존 FleetCharge 테스트 데이터를 넣고 격리 검사를 수행한다. 완료 후 자신이 만든 리소스를 제거한다. 에뮬레이터 내부의 데이터베이스 포트와 준비 상태 확인 포트는 각각 8081, 8080이며, Docker가 각 인스턴스에 서로 다른 호스트 포트를 할당해 127.0.0.1에 연결한다. 두 에이전트가 호스트 포트 8081을 놓고 경쟁하지 않는다.
검사에서는 환경 A가 BAY-7과 agent-a-dispatch를, 환경 B가 BAY-8과 agent-b-dispatch를 기록한다. 두 쓰기가 끝난 뒤 각 환경에서 문서를 개별 조회해 ID, 차고, 픽업 구역, 업데이트 참조를 확인한다. 두 환경의 PDX-02 문서도 확인해 기존 BAY-99 값과 dispatch-099 참조가 유지되는지 검사한다.
그다음 환경 A를 제거하고 새 SDK 클라이언트로 환경 B를 다시 읽는다. B의 데이터가 남아 있어야 한다. 실패한 읽기 확인은 0이 아닌 종료 코드로 끝나며, 소유한 컨테이너를 정리하고 성공 결과 보고서를 만들지 않는다. 로그와 JSON 결과 보고서는 실행마다 고유한 무시 대상 아티팩트 디렉터리에 저장하며, 성공적으로 검증하고 정리한 뒤에만 결과 보고서를 쓴다.
GitHub Actions에서 작업별 검사 실행하기
예제 저장소는 .github/workflows/agentic-ci.yml에 별도의 Agent task CI 워크플로를 추가한다. 작업 매트릭스의 dispatch는 디스패치 단위 테스트와 피드 통합 테스트를 실행하고, fleet-rules는 차량 규칙 단위 테스트와 배송 웨이브 도구 통합 테스트를 실행한다. 각 작업은 GitHub 호스팅 Ubuntu 러너를 별도로 받고, 동적으로 지정된 루프백 포트를 사용하는 자체 에뮬레이터 하나를 시작한다.
에이전트가 변경 사항을 제출하기 전에 같은 검사를 로컬에서 실행할 수도 있다.
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
실행기는 에뮬레이터 주소, 준비 상태 확인 URL, 공개 테스트 키를 테스트 하위 프로세스에 직접 전달한다. 테스트마다 별도 데이터베이스에 알려진 데이터를 넣고 종료 후 정리한다. JUnit 결과, 에뮬레이터 로그, 최종 보고서는 작업마다 고유한 아티팩트 디렉터리에 저장한다. 테스트 통과와 컨테이너 제거 확인이 모두 끝나야 성공으로 보고한다.
워크플로는 fail-fast: false로 한 작업이 실패해도 다른 작업이 끝까지 실행되고 결과를 남기도록 한다. if: always()를 사용해 실패 뒤에도 진단 아티팩트를 올린다. 아티팩트 이름에 작업과 실행 시도를 포함해 병렬 작업끼리 결과를 덮어쓰지 않게 한다. 별도의 isolation-proof 작업은 두 컨테이너의 독립 쓰기와 정리 경계를 확인한다.
기존 Cosmos emulator CI 워크플로는 Agent task CI와 별도로 유지된다. Copilot은 스웜 작업에서 코딩을 수행하며, Ollama는 이 작업에 사용되지 않는다. 개별 작업의 빠른 검사는 통합 애플리케이션 검증을 대체하지 않는다. 병합 전에 관련 검사를 통과하도록 설정해야 한다.
코딩 에이전트와 수명 주기 연결하기
작업마다 전용 Git 워크트리와 기능 브랜치, 소유권이 명확한 에뮬레이터, 코드 리비전에 맞춘 버전 관리 합성 데이터, 별도 로그와 테스트 아티팩트, 성공과 처리 가능한 실패 상황 모두에 대응하는 정리 절차를 할당해야 한다. 작업이 끝나면 에이전트는 변경 사항과 필요한 데이터 계약 변경을 제출하고, 통합 리비전은 별도 환경에서 다시 검사한다.
예제는 데이터베이스 수명 주기와 CI 검사, 결정적 대역 또는 실제 코딩 에이전트를 이용한 스웜 검사를 구현한다. 프로덕션용 코딩 에이전트 조정기는 별도로 마련해야 한다. 풀 리퀘스트가 검토를 기다리는 동안 임시 데이터베이스를 유지할 필요는 없다. 재현 가능한 입력과 검사 근거를 보존한 뒤 리소스를 해제하면 된다.
병렬 작업 전체를 검증하기
경계 검사와 작업 실행기만으로는 병렬 작업이 함께 올바르게 동작한다고 입증할 수 없다. 스웜 검사는 같은 리비전에서 Git 워크트리 세 개를 만들고 각각 별도의 에뮬레이터를 시작한다. 두 작업은 dispatch, 하나는 fleet-rules를 다루며, 각 작업은 자기 주소를 받고 워크트리의 파일을 바꾸고 디스패치 데이터를 기록해 커밋한다. 이후 각 환경의 데이터가 해당 작업의 변경인지 확인하고, 자기 에뮬레이터에서 테스트를 실행한다.
의도적인 대조 검사에서는 결정적 대역 두 개를 하나의 공유 에뮬레이터에서 실행해 쓰기 하나가 사라지는지 확인한다. 이 검사는 모델 동작이 아니라 공유 상태 충돌을 확인한다. 마지막으로 작업 브랜치를 통합 워크트리에 병합하고 새 에뮬레이터에서 통합 테스트를 실행한 다음 컨테이너, 워크트리, 임시 브랜치를 제거한다. 로그와 패치, 테스트 결과는 근거로 보존한다.
기본 설정은 Copilot을 호출하지 않는 결정적 대역이다. 실제 코딩 에이전트를 실행하려면 GitHub Copilot CLI 명령을 전달한다.
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'
실행기는 각 작업에 고유한 COSMOS_* 설정과 SDK 및 테스트 도구가 설치된 SWARM_PYTHON 인터프리터를 전달한다. 실제 에이전트는 소스 변경을 만들고 커밋하며, 자기 디스패치 상태를 기록하고 작업 검사를 통과해야 한다.
데이터베이스 격리는 코드 충돌을 막지 않는다. 실제 에이전트끼리 충돌하면 실행기는 같은 설정의 명령을 통합 워크트리에서 호출해 해결을 시도한다. 병합이 완료되고 부모 커밋 두 개가 모두 남아 있으며, 충돌 표식이 없고 통합 테스트가 통과해야 한다. 해결되지 않은 충돌, 테스트 실패, 에이전트 실행 실패는 검사 실패로 처리한다. 에이전트별 패치와 통합 패치는 여전히 검토해야 한다. 테스트 통과만으로 의미가 의도대로 보존됐다고 증명할 수는 없다.
수동으로 실행하는 실제 Copilot 워크트리 스웜 작업도 agentic-ci.yml에 포함돼 있다. main에서 run_real_agents를 활성화해 실행한다.
gh workflow run agentic-ci.yml --repo AzureCosmosDB/fleetcharge-agent-cosmos-ci --ref main -f run_real_agents=true
이 작업은 저장된 개인 액세스 토큰(PAT)이 아니라 Actions 러너의 기본 GITHUB_TOKEN과 작업 범위가 제한된 contents: read, copilot-requests: write 권한으로 Copilot CLI를 실행한다. 조직 소유자는 조직에 청구되는 Copilot CLI 사용을 허용해야 한다. 작업은 기본 브랜치에서만 실행되며 시간 제한은 60분이다. 테스트한 CLI 버전이 허용하는 최소치인 AI 크레딧 30개로 세션별 한도를 둔다. 작업 세 개와 충돌 해결 세션 최대 두 개의 설정상 한도는 총 150크레딧이며, 실제 소비량을 뜻하지 않는다. 정책, 예산, 모델 접근 권한이 없으면 대역으로 대체하지 않고 실제 작업을 실패 처리한다.
완료된 Actions 실행에서는 실제 Copilot 코딩 작업 세 개를 포함한 다섯 작업이 모두 통과했다. 각 작업 검사는 18개, 18개, 57개 테스트를 통과했지만 디스패치 테스트 묶음끼리 겹치므로 이 수치를 합산할 수 없다. 통합 리비전은 새 에뮬레이터에서 75개 통합 테스트를 통과했다. 스웜 실행기 자체는 작업 준비 시간을 제외하고 140.802초, 약 2분 21초가 걸렸다. 이는 관찰된 결과이며 고정된 테스트 수나 보장된 실행 시간이 아니다.
실제 실행에서 디스패치 에이전트 두 개는 차고 불일치 오류 메시지를 각각 개선했고, 차량 규칙 에이전트는 충전 효율 주석을 추가했다. 두 변경이 app/dispatch.py에서 실제 충돌을 일으켰지만 Copilot이 해결했고, 실행기는 병합 부모 두 개가 모두 포함됐는지 확인한 뒤 통합 검사를 통과시켰다. 세 환경에서 각각 BAY-7, BAY-8, BAY-9를 읽었고 포틀랜드 데이터는 유지됐다. 컨테이너와 임시 워크트리, 브랜치는 제거했으며 패치, 로그, 75개 테스트의 JUnit 결과는 업로드된 아티팩트에 남았다.
수동 대역 작업이 기본값이며 일반 풀 리퀘스트는 코딩 에이전트를 실행하지 않는다. 실제 작업은 기본 제공 MCP 서버와 사용자 지정 지침을 끄고 Copilot의 셸·MCP 도구 환경에서 GITHUB_TOKEN을 제거한다. 그러나 이것은 보안 샌드박스가 아니다. 에이전트는 러너와 Docker에 폭넓게 접근할 수 있으므로 신뢰하고 검토한 코드만 사용해야 한다. 생성된 변경 사항은 임시 로컬 커밋과 업로드된 패치로 남으며, 작업이 직접 푸시하거나 풀 리퀘스트를 병합하지 않는다.
데이터가 아니라 검토한 변경을 병합하기
에이전트의 테스트가 통과해도 임시 데이터베이스의 문서를 공유 환경에 복사할 이유는 없다. 문서는 테스트 상태다. Git에는 애플리케이션 변경과 데이터 계약 변경을 재현하거나 적용하는 데 필요한 지침을 넣는다.
Azure Cosmos DB에서는 새 문서 속성, 갱신된 픽스처, 문서 버전 전환, 컨테이너 인덱싱 정책 정의 등이 이에 해당할 수 있다. 관계형 DDL로 표현되지 않더라도 검토가 필요하다. 버전 관리되는 마이그레이션은 어떤 문서 버전을 받는지, 무엇을 바꾸고 그대로 두는지, 결과를 어떻게 검증하는지 드러내야 한다. 실행이 중간에 멈추거나 반복될 때의 동작도 정해야 한다. 동시 갱신이 가능하다면 ETag를 사용하는 조건부 쓰기로 오래된 전제를 감지해 최신 값을 조용히 덮어쓰지 않도록 할 수 있다.
파티션 키 변경에는 별도의 데이터 이동 계획이 필요하며 단순한 문서 속성 이름 변경으로 취급할 수 없다. 인덱싱 정책 변경도 한 에이전트의 테스트 문서에만 영향을 주지 않으므로 조정해야 한다. 승인된 마이그레이션의 반복 가능한 실행 진입점으로 셸 스크립트를 쓸 수 있지만, 예제는 마이그레이션 실행기나 실제 계정 배포 경로를 구현하지 않는다. 테스트 데이터 초기화 스크립트와 배포 마이그레이션은 분리해야 한다. 폐기 가능한 환경에서 합성 데이터를 다시 만드는 방식을 실제 애플리케이션 데이터베이스 갱신에 사용해서는 안 된다.
Git이 충돌 없이 병합하더라도 두 변경이 애플리케이션에서 함께 동작한다는 뜻은 아니다. 한 에이전트가 새 필드를 필수로 만들고 다른 에이전트가 그 필드 없이 픽스처 생성기를 바꿀 수 있다. 마이그레이션이 같은 속성을 서로 다른 방식으로 수정하거나 인덱싱 정책 변경이 맞지 않을 수도 있다. 격리는 개발 중 데이터 충돌을 막을 뿐 설계 충돌을 해결하지 않는다. 병합 전에 문서 규칙과 컨테이너 정책이 겹치는 부분을 검토하고 필요한 마이그레이션 순서를 정한 뒤, 통합 리비전에서 새 환경을 만들고 코드와 픽스처, 마이그레이션을 함께 테스트해야 한다.
검증 범위와 한계
검사는 같은 데이터 식별자를 사용하는 두 개의 독립 환경에서 경쟁 쓰기를 수행하고, 한 환경을 제거해도 다른 환경의 데이터가 남는다는 점을 확인한다. 스웜 검사는 이를 워크트리 세 개로 확장하고, 공유 에뮬레이터에서 같은 쓰기가 충돌하는 상황과 브랜치 병합 후 통합 테스트 통과를 보여준다.
기본 대역 검사는 서로 다른 파일을 수정하므로 병합 충돌이 없다. 실제 에이전트 실행은 Copilot이 코드 변경을 만들고 별도 데이터베이스를 사용해 통합을 마칠 수 있다는 근거를 더한다. 실제 Git 충돌 한 건도 해결됐다. 그러나 의미적 충돌을 항상 신뢰성 있게 해결한다거나, 임의 규모의 스웜을 지원한다거나, 프로덕션에 적합하다는 점을 입증하지는 않는다. 별도 에뮬레이터 컨테이너도 Docker에 접근할 수 있는 신뢰할 수 없는 코딩 에이전트의 보안 샌드박스가 아니다.
vNext 에뮬레이터 문서는 지원 기능의 일부 범위를 설명하며 인증을 강제하지 않는다고 명시한다. 신뢰할 수 있는 개발 환경에서 게시된 포트는 루프백에만 연결해야 한다. 에뮬레이터 테스트는 실제 계정의 인증, 권한, 처리량, 프로덕션 성능을 검증하지 않는다. 지속적 배포는 별도의 관문이다. 검토된 마이그레이션 동작, 배포 자격 증명, 환경 승인, 실제 서비스 검증을 따로 설계하고 테스트해야 한다. 코딩 에이전트에 폐기 가능한 데이터베이스를 제공한다고 해서 변경 사항을 자동 배포할 권한을 주는 것은 아니다.
기준이 되는 에뮬레이터 CI 안내, 예제인 FleetCharge 저장소, 코딩 에이전트를 위한 Azure Cosmos DB 지침을 모은 Azure Cosmos DB Agent Kit를 참고할 수 있다.
관련 문서로는 Azure Cosmos DB vNext 에뮬레이터, 지속적 통합 워크플로에서 에뮬레이터 사용, Azure Cosmos DB Python SDK, 트랜잭션과 낙관적 동시성 제어, GitHub Actions 매트릭스로 작업 변형 실행, GitHub Actions 아티팩트 저장 및 공유, GITHUB_TOKEN으로 GitHub Actions에서 Copilot CLI 사용, Actions에서 Copilot CLI 인증 및 청구을 볼 수 있다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요