자율 에이전트(Autonomous agents)는 쉬운 부분이다
요약
1인 개발자가 10개의 프로젝트를 효율적으로 관리하기 위해 구축한 '마스터-슈퍼바이저' 시스템을 소개합니다. 개별 프로젝트의 할 일 목록을 통합하여 하나의 작업 대기열(queue)로 관리하고, AI 에이전트가 실행 가능한 '작업 지시(work orders)' 단위로 전환하는 과정을 다룹니다.
핵심 포인트
- 분산된 프로젝트 목록을 하나의 통합된 관리 시스템으로 통합
- 단순 메모가 아닌 AI가 실행 가능한 '작업 지시(work orders)' 개념 도입
- 마스터-슈퍼바이저를 통한 프로젝트 우선순위 자동화
- 에이전트 중심의 워크플로우를 위한 작업 단위 구조화
지난주 어느 날 아침, 나는 수백 통의 이메일이 쌓인 편지함을 열었습니다. 모두 내가 나에게 보낸 것이었고, 대부분은 전날 밤 같은 2분 사이에 발송된 것이었습니다. 이야기는 여기서 끝납니다. 이 이야기에 의미를 부여하려면 내가 어떻게 일하는지부터 시작해야 합니다. 왜냐하면 문제가 된 대부분의 상황은 AI 때문이 아니라, 설정된 작업 방식의 형태에서 비롯되었기 때문입니다.
10개의 프로젝트와 어디를 봐야 할지 모르는 상황
나는 약 10개의 라이브 프로젝트를 운영하는 1인 개발자입니다. 이 프로젝트들은 Mac Studio의 10개 폴더로 존재하며, 나는 프로젝트당 하나의 iTerm 탭을 열어두고 각 탭에서 개별적인 Claude Code 세션을 실행합니다. 내가 프로젝트 사이를 이동하는 동안 보통 2~3개는 동시에 실제 작업을 수행하고 있습니다. 이 부분에 대해서는 이전에 글을 쓴 적이 있습니다. 잘 작동합니다.
문제는 코딩이 아니었습니다. 다음에 무엇을 코딩할지 아는 것이 문제였습니다. 작업들은 두 가지 방향에서 들어왔지만, 유용한 곳으로는 가지 못했습니다. 책상 앞에 있지 않을 때 아이디어가 떠오르면 나 자신에게 이메일을 보냈습니다: 프로젝트 이름, 그리고 무엇을 할지에 대한 한 줄 요약. 그리고 이미 프로젝트에 깊이 몰입해 있는 상태에서 무언가를 발견하면, Claude Code에게 "그 내용을 할 일 목록(to-do list)에 추가해줘"라고 말했고, Claude Code는 해당 프로젝트의 자체 목록에 그것을 추가했습니다.
그렇게 몇 달이 지나자 나에게는 10개의 할 일 목록이 생겼습니다. 10개나 말이죠. 매일 아침 나는 똑같이 무의미한 질문을 던지며 하루를 시작했습니다: 10개의 프로젝트 중 다음 한 시간을 투자할 가장 중요한 곳은 어디인가, 그리고 이 작업들 중 어떤 것을 기계가 나 없이 그냥 수행할 수 있는가? 10개의 목록은 그 질문에 답할 수 없습니다. 10개의 목록은 하나의 목록이 아니라, 서로를 인지하지 못하는 10개의 별개로 분리된 작은 더미일 뿐입니다.
10개 대신 하나의 목록
그래서 7월 중순, 나는 다른 10개의 프로젝트를 살펴보는 것만을 유일한 임무로 하는 새로운 프로젝트를 만들었습니다. 나는 이것을 마스터-슈퍼바이저(master-supervisor)라고 부릅니다. 처음에는 그리 똑똑한 방식은 아니었습니다. 모든 저장소(repo)를 탐색하고, 각 프로젝트가 유지하는 작은 상태(status) 파일을 읽은 다음, 하나의 순위가 매겨진 개요를 구축하는 스크립트였습니다. 덕분에 나는 아침에 10개가 아닌 단 하나를 열어볼 수 있게 되었습니다. 포트폴리오를 위한 비서실장(chief of staff)이지, 또 다른 일손이 아닙니다.
그다음 실제로 의미 있는 변화가 찾아왔습니다. 저는 같은 날 프로젝트별 할 일 목록(to-do lists)을 전부 삭제하고, 관리자(supervisor)가 소유하는 시스템 전체 목록 하나로 대체했습니다. 저는 항목들을 더 이상 '할 일(to-dos)'이라 부르지 않고 '작업 지시(orders)', 즉 작업 지시서(work orders)라고 부르기 시작했습니다. 이름을 바꾼 것 자체가 핵심이었기 때문입니다. 이것들은 더 이상 나 자신에게 남기는 메모가 아니라, 무언가가 가져가서 실행할 수 있는 작업 단위(units of work)입니다. 모든 프로젝트는 여전히 각자의 파일을 가지고 있지만, 이제는 관리자의 저장소(repo)에 존재하며, 낙서장(scratchpad)이 아닌 대기열(queue)로 기능합니다.
모든 것을 결정하는 라벨
작업 지시(order)는 작은 마크다운(markdown) 블록입니다. 제목, 몇 개의 필드, 한두 줄의 설명으로 구성됩니다. 대략 다음과 같은 형태입니다:
## ORDER update-mcp-server
art: autonomous
prio: medium
...
이 중 두 개의 필드가 역할을 수행하며, 첫 번째 필드가 가장 중요합니다. 이 필드는 art라고 불리며, 세 가지 가능한 값을 가집니다:
- autonomous — Claude Code가 아무런 간섭 없이 스스로 수행하고 완료할 수 있습니다.
- dialog — 작업이 진행되는 동안 제가 과정에 참여(in the loop)하기를 원합니다.
- decision — 오직 저만이 결정을 내릴 수 있습니다. 결제 승인, 도메인 구매, 공개 발표 등이 이에 해당합니다.
여기서 저는 솔직해지고 싶은 부분이 있습니다. 왜냐하면 이 내용은 인상적으로 들리는 것과는 정반대이기 때문입니다. 어떤 AI도 작업 지시가 어느 범주에 들어갈지 결정하지 않습니다. 저는
그리고 필드(field)가 누락되었거나 불분명할 때의 기본값은 dialog이며, 결코 autonomous가 아닙니다. 그 비대칭성이 바로 안전 모델(safety model)의 핵심을 한 줄로 요약한 것입니다. 제가 기다리며 머물러 있는 명령은 약간의 시간을 소모할 뿐입니다. 하지만 실행되지 말아야 할 때 스스로 실행되는 명령은 운영 환경(production)에서의 작업 손실을 초래합니다. 따라서 기계는 질문하는 쪽으로 기울어야 하며, autonomous는 가정되는 것이 아니라 획득해야 하는 것입니다.
두 번째 레이블은 우선순위(priority)이며, 솔직히 말씀드리면 이 부분이 제가 가장 파악하지 못한 부분임을 인정합니다. 저는 이를 높음(high), 중간(medium), 낮음(low)으로 직접 설정하며, 자동 정렬 기능이 아직 충분하지 않기 때문에 며칠마다 여전히 직접 전체 대기열(queue)을 다시 훑어봅니다. 한 번의 스캔에서 41개의 자율(autonomous) 명령이 있었고, 그중 13개는 승인(freed)하고 14개는 보류(held)했습니다. 이것은 알고리즘이 아니라 형광펜을 든 저 자신입니다. 이것이 현재의 솔직한 상태입니다.
명령이 발생하는 곳, 그리고 제가 굽히지 않을 단 하나의 규칙
명령은 세 가지 방식으로 도착하며, 이를 둘러싼 대부분의 메커니즘은 하나의 문을 닫아두기 위해 존재합니다. 제가 스스로에게 이메일을 보내는 아이디어와 시스템이 생성하는 알림은 모두 동일한 수신함에 도착하지만, 그곳의 어떤 것도 스스로 명령이 되지는 않습니다. 그것들은 제가 어차피 수신함을 읽으며 직접 분류(triage)하는 과정에서 명령으로 변환됩니다. 이것은 게으름이 아니라 고정된 규칙입니다. 스스로 자율 작업(autonomous task)으로 승격될 수 있는 메일은 열린 문과 같습니다. 왜냐하면 저에게 이메일을 보낼 수 있는 사람이라면 누구든 제 기계에 명령을 내릴 수 있고, 메일을 읽는 AI는 저의 의도와 공격자의 문단을 구분할 수 없기 때문입니다. 메일은 인간이라는 관문(gate)으로 남아야 합니다.
세 번째 방식은 제가 프로젝트 중에 "그것을 목록에 넣어줘"라고 말하는 것이며, 이는 감독자(supervisor)가 자신의 파일 내에서 실제 명령으로 변환하는 공유된 추가 전용(append-only) 메일함을 통해 전달됩니다. 각 파일당 하나의 작성자(writer)가 할당됩니다. 관료주의처럼 들리겠지만, 이것이 이력을 읽기 가능한 상태로 유지해 줍니다. 지난번에 깨달았듯이, 실제로 유효한 규칙의 버전은 문서의 한 줄이 아니라 에러를 반환하는 훅(hook)입니다.
5분간의 야간 근무
이제 제가 잠든 동안 실행되는 부분입니다. 5분마다 스크립트가 실행됩니다. 이 스크립트는 큐(queue)를 살펴보고, 승인되었으며 autonomous로 표시된 최상위 주문들을 가져와서, 각 주문에 대해 제가 처음부터 했어야 했던 일을 수행합니다. 즉, 새로운 git worktree를 생성하여 프로젝트의 메인 브랜치를 별도의 스크래치 디렉토리에 깨끗하게 체크아웃(checkout)합니다. 절반쯤 수정된 내용과 열려 있는 파일들이 있는 저의 작업 복사본(working copy)이 아닙니다. 깨끗하고 격리된 복사본을 사용하여, 기계가 저나 해당 리포지토리(repo)에서 이미 작업 중인 다른 세션과 충돌하는 일이 절대 발생하지 않도록 합니다. 이전 버전은 "바쁜" 프로젝트는 건너뛰었기 때문에 실제로는 차례가 돌아오는 일이 거의 없었습니다. worktree는 공유를 거부함으로써 이 문제를 해결했습니다.
해당 worktree 내부에서 Claude Code를 헤드리스(headless) 모드로 실행하여 주문을 전달하고, 작업이 밤새 폭주하여 리소스를 다 써버리지 않도록 실제 시간 기준 타임아웃(wall-clock timeout)과 사이클당 몇 개의 주문으로 제한을 두어 작업을 진행하게 합니다. 작업이 완료되면 머지(merge)를 시도합니다. 머지가 깔끔하게, 즉 조정할 것이 없는 단순한 패스트 포워드(fast-forward)라면 스스로 완료합니다. 만약 충돌(conflict)이 발생하면, 똑똑한 척하지 않습니다. 브랜치를 그대로 둔 채 제 휴대폰으로 알림을 보내고, 해당 프로젝트를 열어 인간만이 가진 컨텍스트(context)로 문제를 해결하라고 저에게 알립니다. 영리한 충돌 해결 방식은 미묘한 버그가 숨어드는 곳이기에 금지되어 있습니다. 깔끔한 머지 후에는 두 번째 독립적인 검토가 실행되며, 이를 통과하면 프로덕션(production)에 배포합니다. 승인되었다는 것은 곧 라이브(live) 상태가 됨을 의미합니다.
이것이 기계의 작동 방식입니다. 운이 좋은 밤에는 정말로 훌륭하게 작동합니다. 그러다 제가 이것을 완전히 켜두었을 때, "훌륭함" 뒤에 무엇이 숨겨져 있었는지 알게 되었습니다.
스스로 작동하던 밤, 그리고 균열
하룻밤 사이에 그것은 16개의 저장소(repositories)에 걸쳐 66개의 오더(orders)를 구축, 병합 및 배포했습니다. 제가 제 프로젝트라고 생각하는 10개와 그 주변의 더 작은 인프라 저장소들을 포함한 수치입니다. 언어 배너가 배포되었고, 의존성(dependency) 패치가 이루어졌으며, MCP 서버가 구축되었습니다. 제가 일주일 동안 고군분투해야 했을 작업이 아침이 되기도 전에 완료되었습니다. 저는 약 4분 동안 감명받았지만, 곧 모든 균열이 드러났습니다. 그리고 그 균열들은 모두 같은 곳에 있었습니다. 작업 자체가 아니라, 기계가 그 작업에 대해 저에게 말해준 방식에 말입니다.
백 가지의 좋은 소식들. 저는 그것에게 친절한 습관을 부여했습니다. 오더가 완료되면 이메일을 보내고, 결정이 필요한 경우에는 이메일을 보내라는 것이었습니다. 하룻밤 사이에 그것은 백로그(backlog)를 처리했고, 저는 72개의 "완료" 이메일과 28개의 "당신의 확인이 필요함" 이메일을 받으며 잠에서 깨어났습니다. 이 숫자들을 다시 읽어보십시오. 핵심은 바로 그 28개였습니다. 기계가 올바르게 멈춰서 질문을 던진 decision 오더들이었기 때문입니다. 하지만 그것들은 "이 작업에 당신이 있을 필요는 없었습니다"라는 내용뿐인 72개의 메시지 아래에 파묻혀 있었습니다. 새벽 01:44에 저는 반쯤 잠든 채 짜증이 난 상태로 저 자신에게 이메일을 한 통 더 보냈는데, 그것은 제가 지난 몇 달간 작성한 것 중 가장 날카로운 디자인 리뷰(design review)였습니다. "완료 이메일은 끄고, 실패한 경우에만 보내라." 이제 성공 사례는 로그(log)로 전송됩니다. 오직 실패와 실제 결정이 필요한 경우에만, 권장 사항이 포함된 질문의 형태로 하나씩 저에게 도달합니다. 분류는 이미 올바르게 되어 있었습니다. 저는 단지 지루한 99%와 중요한 1%를 같은 볼륨으로 외치고 있었을 뿐이며, 모든 것을 보고하는 채널은 아무것도 보고하지 않는 것과 같습니다.
양치기 소년의 경보. 그런 일이 일어나는 동안 제 휴대폰은 온종일 빨간색으로 울려댔습니다. 완벽하게 통과했던 배포(deploys)들이 계속해서 승인 실패(acceptance failed)를 일으켰기 때문입니다. 한 주문은 프로덕션(production)에 아무것도 보내지 않는 정리 작업(cleanup task)이었는데, 검증 레이어(checking layer)는 그 사실을 알지 못했습니다. 레이어는 결코 일어날 수 없는 배포를 요구하며 5분마다 비명을 질러댔습니다. 또 다른 배치(batch) 전체의 문제는 단 하나의 문자 때문이었습니다. 셸 파이프라인(shell pipeline) 앞의 !가 잘못된 부분의 종료 코드(exit code)를 부정하여, 통과했던 체크가 실패한 것처럼 보이게 만들었고, 모든 것이 정상일 때 정확히 경보가 울렸습니다. 이 중 그 어떤 것도 AI의 문제는 아니었습니다. 에이전트(agents)들은 작업을 올바르게 수행했습니다. 작업을 검증하기 위해 제가 작성한 레이어가 버그가 있는 부분이었습니다. 그리고 모든 버그는 동일한 방향을 가리키고 있었습니다. 즉, '좋은 것'을 '나쁜 것'으로 판정했다는 점입니다. 해결책은 추측(guess)이 아니라 현실(reality)을 확인하는 것이었습니다. 커밋(commit)이 존재하는가, 파일이 존재하는가, URL이 실제로 응답하는가와 같은 방식 말입니다.
조용한 반대 상황. 이것이 저를 패치(patching)를 멈추고 다시 생각하게 만든 지점이었습니다. 이틀 전 밤, 동일한 레이어가 반대 방향으로 실패했습니다. 오타 하나가 리뷰 스크립트(review script)를 중단시켰고, 충돌(crash)이 발생한 스크립트는 정상적으로 실행되어 "아니오(no)"라고 답하는 스크립트와 동일한 종료 코드(exit code)를 반환했습니다. 기계는 "리뷰어가 반대함"과 "리뷰어가 쓰러짐"을 구분할 수 없었습니다. 그래서 기계는 안전해 보이는 선택을 하며 작업을 중단시켰습니다. 조용하게 말이죠. 18개의 주문이 프로덕션에 도달하지 못한 채 완료된 것으로 표시된 채 머물러 있었습니다. 해결책은 세 가지 서로 다른 상황을 하나로 통합하는 것을 멈추는 것이었습니다. 이제 충돌(crash), 반대(objection), 통과(pass)는 세 개의 별개 신호이며, 충돌이 신중하게 고려된 "아니오"처럼 보이는 일은 다시는 없을 것입니다.
다른 각도에서 동일한 교훈을 주는 네 번째의 더 작은 사례가 있었습니다. 한동안 디스패처(dispatcher)는 각 프로젝트 큐(queue)에서 오직 가장 상단의 단일 주문만을 확인했습니다. 만약 그 최상단 주문이 제가 대기 중인 dialog 작업이라면, 일곱 번째 주문이 승인되어 준비된 상태라 할지라도 그 뒤의 전체 큐는 동결되었습니다. 한 프로젝트는 승인되지 않은 네 개의 주문이 다른 모든 것 앞에 쌓여 있어, 그 어떤 것도 실행되지 못한 채 멈춰 있었습니다. 국소적으로는 정확했지만, 전역적으로는 막혀 있었으며, 제가 직접 찾아가기 전까지는 보이지 않았습니다.
실제로 어려운 부분은 무엇인가
이 사례들을 나란히 놓고 보면, 이들은 여러 가지 모습으로 변장한 하나의 문제입니다. 아무런 의미도 없었던 "모든 것이 실패했다"는 하루. 중요한 18개가 실행되지 않았음에도 "모든 것이 완료되었다"고 말하는 밤. 중요한 28개가 보이지 않았던 수백 통의 이메일. 가득 차 보이지만 아무것도 움직이지 않는 큐. 매번, 작업 자체는 괜찮았습니다. 작업에 대한 보고, 라우팅(routing), 순서 지정, 그리고 무엇이 저에게 도달할지를 결정하는 요소들이 잘못되었습니다.
에이전트(agent)가 작업을 수행할 수 있는지 여부는 대부분 해결되었으며, 예상보다 빠르게 해결되었습니다. 하룻밤 사이에 66번의 배포(deploys)를 수행하는 것은 더 이상 인상적인 부분이 아닙니다. 해결되지 않은 부분은 그 주변의 모든 것입니다. 작업을 열 곳이 아닌 한 곳으로 모으는 것, 기계가 단독으로 처리할 수 있는 것을 정직하게 라벨링(labelling)하는 것, 그리고 무엇보다 저에게 돌아오는 채널입니다. 에이전트가 "모두 이상 없음"이라고 말할 때 제가 그것을 신뢰할 수 있는지, 그리고 에이전트의 "당신이 필요합니다"라는 메시지가 스스로의 박수갈채 속에 빠져 허우적거리는 대신 저에게 도달할 수 있는지 말입니다. 이것들을 잘못 설정한다면 자율성(autonomy)은 자율성이 아니라, 당신이 이제는 떠나기가 두려워지는 방 안에서 기계가 작업을 수행하고 있는 상태일 뿐입니다.
참고로, 이러한 안전성은 프롬프트(prompts)에 들어있지 않습니다. 그것은 종료 코드(exit codes)와 파일 잠금(file locks), 그리고 의심스러울 때 실행을 거부하는 기본 설정(default)에 존재합니다. 제가 각 프로젝트를 위해 작성하는 브리핑(briefing)은 '이유'를 설명하며 작성할 가치가 있지만, 실제로 버팀목이 되는 부분은 피곤한 세션(session) 중에도 스스로를 설득하여 빠져나갈 수 없는 부분들입니다.
제 것은 수정된 버전을 사용한 지 며칠 지나지 않았고, 아직 아무것도 증명되지 않았습니다. 하지만 그 비율이 진짜 뉴스입니다. 실행하는 것은 거의 공짜나 다름없었습니다. 제 인생의 이틀 밤은 이 녀석이 자신이 무엇을 했는지에 대해 저에게 진실을 말하는 법을 가르치는 데 온전히 쓰였습니다. 만약 당신이 Claude Code를 통해 하나 이상의 프로젝트를 실행하고 있다면, 당신이 아직 알아냈든 아니든, 당신의 시간 또한 그곳에 소모되고 있을 것입니다. 만약 저보다 더 깔끔한 해답을 만들어내셨다면, 저는 진심으로 그것을 보고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기