내가 소유한 모든 저장소에 예산과 킬 스위치를 갖춘 AI 에이전트에게 야간 머지(Merge) 권한을 부여한 이유
요약
1인 개발자가 유지보수 부채를 해결하기 위해 GitHub Actions와 AI 에이전트를 활용하여 자동화된 야간 머지 시스템을 구축한 사례를 소개합니다. 예산 제한, 티어별 권한 설정, 킬 스위치 설계를 통해 에이전트의 자율성과 안전성을 동시에 확보하는 방법을 다룹니다.
핵심 포인트
- 티어 시스템을 통한 단계별 권한 부여(기계적 작업 vs 비즈니스 로직)
- 예산 및 실행 횟수 제한을 통한 에이전트 통제
- 파일 커밋 방식의 단순한 킬 스위치 설계
- 실행 중단 시에도 상태를 기록하는 보고 우선(report-first) 방식
나는 약 20개의 운영 및 지원 저장소(repos)를 혼자 관리하고 있습니다. 큐레이션된 데이터 피드가 있는 콘텐츠 사이트, 몇 개의 예약된 스크래퍼(scrapers), 일부 Cloudflare Workers, 그리고 그 주변에 쌓이는 일반적인 반수면 상태의 도구들(tooling)이 그것입니다. 운영 팀(ops team)은 없습니다. 1인 관리 자산에서의 유지보수 부채(maintenance debt)는 예고 없이 찾아오지 않습니다. 그저 당신이 관리하지 않았던 무언가가 필요한 날이 올 때까지 조용히 복리로 쌓일 뿐입니다.
그래서 나는 유지보수를 수행할 로봇을 만들었고 — 이 부분에서 사람들이 반응하는데 — 그 로봇에게 머지(merge) 권한을 주었습니다.
매일 밤 자정에 GitHub Actions 작업이 깨어나 내 조직(org)의 모든 저장소를 조사하고, 할 수 있는 것을 치유합니다. 풀 리퀘스트(pull requests)를 열고, 스스로 머지하며, 사람의 손길이 필요한 모든 것에 대해 이슈(issues)를 등록하고, 나에게 보고서를 작성합니다. 나는 이 저장소의 이름을 make-me-better라고 지었는데, 이는 내 자신의 규율에 대해 내가 어떻게 생각하는지를 말해줍니다.
탭을 닫기 전에 알아두세요: 이 로봇은 예산(budget), 티어(tier) 시스템, 그리고 킬 스위치(kill switch) 하에서 작동합니다. 이 세 가지가 이 글의 핵심입니다. 에이전트(agent) 자체는 쉬운 부분입니다.
에이전트가 따르는 규칙
프롬프트(prompt)는 에이전트에게 세 가지 단계의 허용된 행동을 부여합니다:
- Tier 1 — 기계적(mechanical). 누락된 설정(config)의 스캐폴딩(scaffolding) 생성, 메모리 디렉토리 추가, 명확하게 정해진 형태를 가진 보일러플레이트(boilerplate) 작성 등. 에이전트는 이를 스스로 열고 머지할 수 있습니다.
- Tier 2 — 판단(judgment). 비즈니스 로직(business logic)을 건드리는 모든 것은 PR이 아닌 이슈(issue)로 처리됩니다. 프롬프트의 규칙은 "비즈니스 로직을 절대 추측하지 말 것"입니다.
- Tier 3 — 자기 수정(self-modification). 에이전트는 자신의 코드 — 관찰 스크립트(observation script), 워크플로우(workflow), 심지어 자신의 프롬프트까지 — 를 변경할 수 있습니다. 하룻밤에 최대 한 번만 허용됩니다.
티어(tiers) 외에도 엄격한 제한(hard caps)이 있습니다: 하룻밤에 치유 PR 3개, 이슈 3개, 자기 수정 1개로 제한됩니다. 에이전트는 절대 아무것도 삭제하지 않으며 강제 푸시(force-push)도 하지 않습니다. 그리고 킬 스위치(kill switch)는 의도적으로 단순하게 설계되었습니다: 저장소 루트에 PAUSE라는 이름의 파일을 커밋하면 작동을 멈춥니다. 나는 새벽 2시에 위치를 기억해야 하는 대시보드나 설정 플래그(config flag)를 원하지 않았습니다.
즉시 제 가치를 증명한 또 다른 설계 결정은 프롬프트가 '보고 우선(report-first)' 방식이라는 점입니다. 실행이 도중에 중단되더라도 보고서는 작성되어 커밋됩니다. 커밋 단계는 if: ${{ !cancelled() }} 조건에서 실행되기 때문입니다. 충돌(crash)이 발생한 실행이라도 충돌 전까지 무엇을 확인했는지는 알려줍니다. 당신이 잠든 동안 작동하는 로봇을 디버깅할 때는, 침묵하는 무덤보다는 불완전한 증거라도 있는 편이 낫습니다.
점검의 밤: 네 번의 실패, 그리고 한 단계씩 깊어지는 교훈
첫 번째 밤은 제가 내린 판단 착오들을 하나씩 벗겨내는 과정이었습니다.
Run 1은 제가 관찰 스크립트(observation script)에 작성한 쉘 버그(shell bug)로 인해 중단되었습니다. 저는 모든 곳에 다음과 같은 코드를 작성해 두었습니다:
last_commit=$(gh api "repos/$OWNER/$r/commits?per_page=1" -q '...' 2>/dev/null || echo "")
겉보기에는 괜찮아 보입니다. 하지만 그렇지 않습니다. 만약 gh api가 종료되기 전에 부분적인 JSON 에러 본문(예를 들어 빈 저장소에 대한 409 에러)을 작성한다면, || echo는 기존 출력을 대체하는 대신 그 부분적인 출력 뒤에 대체값(fallback)을 *추가(append)*해 버립니다. 결과적으로 {"message":"Git Repository is empty."}와 대체값이 결합된 형태가 되는데, 이는 유효한 JSON도 아니고 의도한 대체값도 아니기에 이후의 모든 단계가 마비됩니다. 해결책은 먼저 할당을 수행하고, 할당 자체가 실패했을 때만 대체값을 사용하는 것입니다:
last_commit=$(gh api "..." -q '...' 2>/dev/null) || last_commit=""
저는 90년대부터 간간이 쉘 스크립트(shell scripts)를 작성해 왔습니다. 그런데도 이런 실수를 저질렀습니다. 같은 파일 내에서 세 번이나 말이죠.
Run 2는 토큰 교환(token exchange) 과정에서 실패했습니다. 액션(action)이 요구하는 OIDC 흐름(flow)을 설정해두지 않았던 것이 원인이었으며, 이는 제 PAT(Personal Access Token)를 명시적으로 전달함으로써 해결했습니다.
Runs 3 and 4는 흥미로운 사례였습니다. 에이전트는 완벽하게 실행되었고 전체 보고서를 작성했지만, 푸시(push)를 할 수 없었습니다. 403 에러가 발생한 것입니다. 제가 설정한 세분화된 PAT(Fine-grained PAT)는 거의 모든 것에 대해 읽기 전용(read-only) 권한만 가지고 있었습니다. 의도적으로 권한을 아주 짜게 설정해두고는 그 사실을 잊고 있었던 것입니다. 여기서 기록할 만한 판단 착오가 하나 있습니다. 나중에 에이전트가 자신의 워크플로(workflow) 파일을 수정하려고 시도했을 때, 저는 수년간의 클래식 GitHub 토큰 경험을 바탕으로 workflow 스코프(scope)가 필요할 것이라고 "알고" 있었고, 그것을 찾으러 나섰습니다. 하지만 세분화된 PAT에는 workflow 스코프가 없습니다. 대신 이들은 Workflows라는 이름의 저장소 권한(repository permission)을 통해 워크플로 파일 쓰기를 제어합니다. 그리고 세분화된 PAT의 권한을 편집해도 토큰 값은 그대로 유지되므로, 어디에도 다시 붙여넣을 필요가 없습니다. 25년간 축적된 지식은 정말 큰 자산이지만, 조용히 만료되어 버린 부분도 존재합니다.
스스로 예산을 초과해버린 밤 — 그리고 나를 신뢰하게 만든 거절
첫째 날 밤, 작업은 턴 제한(turn ceiling)에 여섯 번이나 부딪혔고, GitHub은 충실하게 이를 재시작했습니다. 재시작될 때마다 시스템은 자신이 야간 제한(nightly caps) 내에 있는지 확인할 수 있는 유일한 방법인 "열려 있는(open)" heal PR(Pull Request)의 개수를 세는 방식으로 확인했습니다. 하지만 머지(merged)된 PR은 그 목록에서 사라집니다. 따라서 각 실행(incarnation)은 깨끗한 상태에서 시작되었고, 아침이 되었을 때 밤사이 총합은 4개의 heal PR과 2개의 자체 수정(self-modifications)으로 집계되어 두 제한 모두를 초과했습니다. 머지된 내용 중 틀린 것은 없었습니다. 예산은 여전히 자신의 유일한 임무, 즉 "실행(run)"이 아닌 "밤(night)"의 범위를 제한하는 데 실패했습니다. 충돌 시 초기화되는 제한은 제한이 아니라 권장 사항일 뿐입니다.
다음에 일어난 일이 바로 이 글이 존재하는 이유입니다. 에이전트는 자신의 보고서에서 제한 추적의 허점을 진단했고, 해결책(검색을 통해 열린 목록이 아닌 밤사이의 PR을 누적하여 계산할 것)을 작성했습니다. 그러고 나서 에이전트는 그 해결책의 구현을 "거절"했습니다. 왜냐하면 그날 밤의 자체 수정 예산이 이미 소진되었기 때문입니다. 에이전트는 해결책을 표시(flag)해두고 멈췄습니다.
자신의 거버넌스(governance)에서 결함을 찾아내고, 예산 없이는 그것을 수정하지 않는 시스템이야말로 그 예산이 의미를 갖는 시스템입니다.
정상 상태: 스스로 작업을 큐에 쌓고 수행함
세 번의 깨끗한 밤이 지나고 나니, 루프(loop)는 다음과 같은 모습이 되었습니다: 어느 날 밤의 보고서에는 "향후 변경 사항으로서 가치 있음: 아카이브된 저장소(archived repos)는 어차피 PR을 받을 수 없으므로 건너뛸 것"이라고 적혀 있었습니다. 그다음 날 밤, 단 하나의 자기 수정(self-modification) 슬롯은 정확히 그 수정 사항에 할당되었고, bash -n으로 검증된 후 머지(merge)되었습니다. 보고서는 심지어 다음 실행 시 무엇을 확인해야 하는지도 알려주었습니다: 즉, 4개의 아카이브된 저장소가 조사 대상에서 실제로 사라졌는지 확인하는 것입니다.
그 과정에서 이 에이전트는 제가 직접 검토했을 때 놓쳤던 제 코드의 버그를 수정했고, 지표 중 하나가 조용히 잘못된 것을 측정하고 있다는 사실(변수는 "마지막 메모리 업데이트"라고 되어 있었으나, API 쿼리는 다르게 나타남)을 포착했으며, "작업이 필요한 저장소" 중 5개가 아카이브되었거나 중단(deprecated)되었다는 것을 발견했을 때 자신의 백로그(backlog) 카운트를 아래로 수정했습니다. 이는 바로 전날 밤 에이전트 스스로가 만들어낸 과다 집계였습니다. 에이전트는 자신의 잘못된 데이터를 스스로 표시(flag)합니다. 제가 관리했던 대부분의 인간은 그렇게 하기 위해 코칭이 필요했습니다.
아무도 경고해주지 않은 실패 모드
며칠 지나지 않아, 제 로컬 클론(local clones) 8개가 원격 저장소(remotes)보다 뒤처지게 되었습니다. 당연한 결과였습니다. 로봇은 매일 밤 GitHub에 커밋(commit)을 수행하며, 제 노트북이 존재하는지조차 모르기 때문입니다. 제가 가졌던 모든 워크플로우 습관은 오직 _나_만이 유일한 작성자라는 가정하에 만들어진 것이었습니다. 이제 잠들지 않는 두 번째 저자가 생겼고, 무언가를 건드리기 전 git pull을 하는 것은 위생의 문제를 넘어 생존의 문제가 되었습니다. 만약 여러분이 이런 시스템을 구축한다면, 팀에게 알리십시오. 설령 저처럼 팀이 곧 자기 자신일지라도 말입니다.
훔쳐야 할 것들
- 실행(run)당이 아닌, 밤(night)당 횟수 제한 — 그리고 크래시(crash)나 재시작(restart) 후에도 유지되는 소스에서 이를 카운트하십시오.
- 자율성(autonomy)의 계층화. 기계적인 변경은 머지 권한을 부여합니다. 판단(judgment)은 이슈(issue)를 생성하게 합니다. 자기 수정(self-modification)에는 단 1회의 예산(budget)을 할당하십시오.
- 보고서 우선(Report-first). 부분적인 보고서를 남기고 종료된 실행은 디버깅(debug)이 가능합니다. 아무것도 남기지 않고 종료된 실행은 강령술(séance)과 같습니다.
- 가동 부품이 없는 킬 스위치(kill switch).
PAUSE라는 이름의 파일을 만드십시오. 스트레스 상황에서 이보다 더 영리한 방법을 떠올리기는 어려울 것입니다. - 사후 분석(postmortem)을 작성하게 하십시오. 매일 밤 작성되는 보고서는 이 글에 언급된 모든 버그(에이전트 자신의 버그를 포함하여)가 실제로 포착된 곳입니다.
불편한 코다(coda)
판단 착오에는 디프(diff)가 첨부되지 않습니다. 이 모든 것을 구축하는 동안, 세 건의 완료된 유료 제안서(pitches)가 전송 대기 상태로 35일 동안 제 초안 폴더에 머물러 있었습니다. 자동화는 진정으로 구축할 가치가 있었습니다. 하지만 제가 이를 구축한 이유 중 일부는 로봇을 다듬는 것이 편안한 반면, 사람에게 돈을 요구하는 것은 그렇지 않기 때문이었습니다. 이제 이 자산(estate)은 매일 밤 스스로 치유됩니다. 당신을 대신해 '전송' 버튼을 눌러주는 에이전트는 아무도 만들지 않았습니다. 그건 당신이 직접 실행해야 하는 영역이기 때문입니다.
저는 클라우드 및 인프라 아키텍트(cloud and infrastructure architect)이며, 25년의 엔터프라이즈 경력을 바탕으로 현재 1인 컨설팅을 운영하고 있습니다. 귀하의 팀에 이미 귀하가 겪고 있는 문제의 엔터프라이즈 버전을 경험해 본 전문가가 필요하다면, jennifer@jenatech.io로 연락해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기