나의 두 AI 작업이 동일한 마우스를 두고 계속 싸웠던 이유
요약
데스크톱 자동화를 수행하는 여러 AI 에이전트가 동일한 물리적 리소스(마우스, 키보드 등)를 동시에 사용하려 할 때 발생하는 충돌 문제를 다룹니다. 단순한 병렬 실행이 아닌, 리소스 점유 상태를 인식하는 스케줄링의 필요성을 설명합니다.
핵심 포인트
- 데스크톱 제어는 물리적 경계가 있어 병렬 처리가 불가능함
- 단순 실행(run) 단위가 아닌 리소스(resource) 중심의 상태 관리가 필요함
- 독점 리소스 필요 시 작업을 직렬화하는 리소스 인식 스케줄링 도입
- 상태 확인 요청 시 리소스를 직접 건드리지 않고 기존 상태로 답변
내 로컬 AI 어시스턴트(AI assistant)가 Windows 앱을 조작하는 법을 배운 바로 그 순간, 나는 새로운 버그에 직면했다.
모델 버그가 아니었다. 프롬프트(prompt) 버그도 아니었다.
트래픽(traffic) 버그였다.
한 작업은 데스크톱을 통해 사이트에 로그인하려고 하고 있었다. 다른 작업은 다른 앱을 열고 싶어 했다. 두 요청 모두 유효했다. 둘 다 "진행 중(in progress)"이었다. 그리고 둘 다 동일한 물리적 마우스, 키보드, 화면을 사용하려고 시도하고 있었다.
그 순간 어시스턴트는 소프트웨어처럼 느껴지는 것이 아니라, 노트북 한 대를 두고 싸우는 두 명의 인턴처럼 느껴지기 시작했다.
첫 번째 버전은 여러 실행(runs)이 있다는 것은 알았지만, 하나의 공유된 데스크톱은 알지 못했다
CliGate에서 나는 이미 상주형 어시스턴트(resident assistant)의 형태를 갖추고 있었다: 백그라운드 실행(background runs), 작업 기록(task records), 채널 대화(channel conversations), 런타임 위임(runtime delegation), 그리고 Windows에서의 데스크톱 자동화(desktop automation).
문제는 "여러 작업(multiple tasks)"과 "여러 데스크톱 작업(multiple desktop tasks)"이 같은 것이 아니라는 점이었다.
대부분의 작업은 병렬(parallel)로 잘 실행될 수 있다. 코딩 작업이 파일을 편집하는 동안 다른 작업이 날씨를 확인하거나 문서를 요약할 수 있다. 하지만 데스크톱 제어는 물리적인 경계다. 활성화된 포인터(pointer)는 하나뿐이고, 포커스된 창(focused window)도 하나이며, 키보드 대상(keyboard target)도 하나뿐이다.
만약 시스템이 모든 실행을 동일하게 병렬로 취급한다면, 데스크톱 작업은 동시성(concurrent)을 갖는 것처럼 느껴지지 않는다. 그것은 파괴적으로 느껴진다.
하나가 포커스(focus)를 뺏을 수 있다. 다른 하나는 잘못된 창을 클릭할 수 있다. 세 번째는 이미 유사한 실행이 있다고 판단하여 엉뚱한 것을 완전히 취소해 버릴 수도 있다.
버그는 활동(activity)과 리소스 소유권(resource ownership)을 혼동한 것이었다
기존의 사고 모델은 너무 얕았다:
대화(conversation) -> 활성 실행(active runs) -> 하나를 취소할지 여부 결정
이것은 상태 표시에는 작동할 수 있다. 하지만 데스크톱 스케줄링(desktop scheduling)에는 충분하지 않다.
내가 실제로 필요했던 것은 어시스턴트가 참조할 수 있는 별도의 진실(truth)이었다:
리소스(resource): 데스크톱(desktop)
소유자(holder): 실행 X (run X)
대기자(waiters): 실행 Y (run Y), 실행 Z (run Z)
이것은 질문을 다음과 같이 바꾸었다:
- "다른 활성 실행이 있는가?"
에서 다음과 같이 바꾸었다:
- "현재 데스크톱이 다른 실행에 의해 점유되어 있는가? 만약 그렇다면, 이 작업은 대기해야 하는가, 상태(state)로부터 답변해야 하는가, 아니면 사용자가 명시적으로 중단을 요청했으므로 취소되어야 하는가?"
그것이 훨씬 더 유용한 질문입니다.
해결책은 일괄 취소가 아닌 리소스 인식 스케줄링(resource-aware scheduling)이었습니다
결국 저는 단순한 규칙 세트로 이동하게 되었습니다.
두 작업이 동일한 독점 리소스(exclusive resource)를 필요로 하지 않는다면, 병렬(parallel)로 실행되도록 둡니다.
만약 두 작업 모두 데스크톱을 필요로 한다면, 직렬화(serialize)합니다.
사용자가 상태(status)를 묻는다면, 데스크톱을 다시 건드리는 대신 기존 실행(run) 상태로부터 답변합니다.
사용자가 "중지"라고 말하면, 대상 실행을 명시적으로 취소합니다.
지나고 보니 당연한 소리처럼 들리겠지만, 이것이 동작을 크게 바꾸어 놓았습니다.
다른 실행을 발견하고 그것을 죽이려고 시도하는 대신, 이제 어시스턴트는 데스크톱을 큐에 대기 가능한(queueable) 리소스처럼 취급할 수 있습니다. 하나의 작업이 이를 점유합니다. 다음 데스크톱 작업은 대기합니다. 점유자가 작업을 마치면, 다음 작업이 자동으로 시작됩니다.
이것은 인간 어시스턴트가 행동하는 방식에 훨씬 더 가깝습니다. 누군가 다른 앱을 열어달라고 요청했다고 해서 로그인 흐름(login flow)을 취소하지는 않을 것입니다. 대신 이렇게 말하겠죠: "지금 이 작업을 수행 중입니다. 바로 다음에 다음 데스크톱 단계를 진행하겠습니다."
또한 중단(interruption)과 관련된 UX를 깔끔하게 정리해주었습니다
기분 좋은 부수 효과는 긴 작업 중에도 어시스턴트와 대화하기가 더 쉬워졌다는 점입니다.
데스크톱 작업이 실행되는 동안에도 저는 여전히 다음과 같이 물을 수 있습니다:
- 마지막 실행은 어떻게 되었나요?
- 얼마나 남았나요?
- 리포지토리(repo)에 스크립트도 작성해 줄 수 있나요?
이러한 요청들이 모두 데스크톱 흐름을 중단해야 하는 이유로 취급되어서는 안 됩니다.
어시스턴트는 실행 상태(run state)로부터 상태 질문에 답할 수 있고, 데스크톱이 아닌 작업은 병렬로 수행하며, 진정으로 동일한 물리적 제어 장치가 필요한 것들만 큐(queue)에 쌓을 수 있습니다.
덕분에 제품이 깨지기 쉬운 자동화 데모처럼 느껴지지 않고, 제한된 손을 가진 실제 운영자(operator)처럼 느껴지게 되었습니다.
제가 유지하고 있는 규칙
에이전트가 실제 데스크톱을 제어할 수 있다면, 다음의 차이점을 이해해야 합니다:
- 병렬 작업 (parallel work)
- 독점 리소스 (exclusive resources)
- 명시적 취소 (explicit cancellation)
- 단순 상태 후속 질문 (simple status follow-ups)
이러한 구분이 없다면, 동시성(concurrency)은 그저 충돌(collision)을 뜻하는 또 다른 단어가 될 뿐입니다.
이것은 현재 제가 Claude Code, Codex CLI, Gemini CLI, 채널(channels), 데스크톱 자동화(desktop automation), 그리고 그 상단에 상주하는 어시스턴트 레이어(assistant layer)를 위해 사용하는 로컬 제어 평면(local control plane)인 CliGate를 형성하는 방식의 일부가 되었습니다.
이 프로젝트는 여기서 오픈 소스로 공개되어 있습니다: CliGate.
만약 여러분이 로컬 에이전트(local agents)를 구축하고 있다면, 데스크톱을 그저 또 다른 도구 호출(tool call)로 취급하고 있습니까, 아니면 스케줄링(scheduling)이 필요한 리소스(resource)로 취급하고 있습니까?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기