AI 자동화의 진짜 위험은 코드가 아니다 — Discord 승인 + launchd로 통제권 설계하기

AI 자동화의 진짜 위험은 코드가 아니다 — Discord 승인 + launchd 통제권 설계

📚 AI 직원 회사 빌드기 — 4부작 시리즈

  • Mac Studio 128GB로 AI 직원 회사 만든 이유

  • ② 외부 부작용 통제 — Discord 승인 + launchd (현재 글)

  • ③ claude -p 6-step 체이닝 — V3.5 + Phase 2 트랩 (이번 주 발행 예정)

  • ④ 30시간 회고 — 토큰·막힌 5번·다음 6주 KPI (다음 주 발행 예정)

AI 자동화 시스템을 처음 설계할 때 대부분 이렇게 생각한다. 코드만 잘 짜면 된다고. 테스트 커버리지를 높이고, 에러 핸들링을 꼼꼼히 하고, 엣지 케이스를 처리하면 안전하다고. 그런데 실제로 자동화 시스템을 운영해보면, 가장 무서운 실패는 코드 버그가 아닌 곳에서 온다.

이런 경험, 한 번쯤 있을 것이다

프로덕션에서 자동화 스크립트가 예상보다 훨씬 많이 실행된 걸 뒤늦게 발견한 적 있다. 외부 API를 수십 번 호출해버렸거나, 콘텐츠가 승인 전에 발행됐거나, “이 정도면 안전하겠지”하고 켜뒀다가 당황한 경험 말이다.

자동화를 끄는 것이 켜는 것보다 훨씬 어렵다는 것도 안다. 무엇을 멈춰야 하는지, 비상 정지 버튼이 어디 있는지 불명확한 시스템은 문제가 생겼을 때 더 큰 문제를 만든다.

이 글은 그 불안감의 정체와, 코드 이전에 설계해야 할 것이 무엇인지에 관한 것이다.

코드 품질은 필요조건이지 충분조건이 아니다

AI 에이전트가 자동으로 블로그 주제를 제안하고, QA를 거쳐 Discord 카드로 승인 요청을 보내는 시스템을 구축했다. 65개 이상의 유닛 테스트가 전부 통과했다. 드라이런도 end-to-end 정상 작동했다.

그런데 이 상태에서 “이제 켜도 됩니까?”라는 질문에 바로 “예”라고 답할 수 있는가?

코드 품질은 필요조건이다. 하지만 자동화 시스템의 안전성은 다른 차원의 문제다. 코드가 의도대로 동작하는 것과, 자동화가 의도한 결과를 만드는 것 사이에는 간극이 있다. 그 간극을 메우는 게 통제권 설계다.

자동화 시스템의 진짜 위험은 두 가지다.

  • 범위 초과: 자동화가 예상보다 많은 외부 동작(발행, API 호출, 결제)을 수행한다.

  • 루프 탈출: 인간이 승인하지 않은 것이 실행된다.

두 번째가 더 위험하다. 코드 버그는 고칠 수 있지만, 루프 밖에서 실행된 동작은 되돌리기 어렵다. OAuth 연동 전까지는 실 발행 금지, 이런 경계선이 코드 바깥에서 설정되는 것처럼.

Discord 승인 체인, 인간이 루프 안에 있다는 것

이 시스템에서 모든 자동화 산출물은 Discord 승인 채널을 거친다. AI가 제안한 블로그 주제, QA를 통과한 쇼츠 스크립트, 스레드 초안. 전부 Discord 카드로 올라오고, 사람이 응답하지 않으면 실행되지 않는다.

구현의 핵심은 환경 변수 하나에 있다.

`# approval_card.py
WEBHOOK_ENV = "DISCORD_APPROVAL_WEBHOOK"

def send_to_discord(message, webhook_url=None):
    url = webhook_url or os.environ.get(WEBHOOK_ENV)
    if not url:
        # webhook 없으면 stdout 덤프 — 전송하지 않는다
        print("---[dry-run: DISCORD_APPROVAL_WEBHOOK not set]---")
        print(message)
        return True
`

DISCORD_APPROVAL_WEBHOOK이 없으면 Discord로 전송하지 않고 stdout에 덤프한다. 이건 단순한 fallback이 아니다. “연결이 없으면 발행하지 않는다”는 보수적 기본값이다. 환경 변수가 설정된다는 건 사람이 명시적으로 채널을 만들고 webhook을 구성했다는 뜻이기도 하다.

승인 대기열에는 48시간 TTL이 걸려 있다. SQLite 스키마에서 status의 기본값은 'pending'이고, 48시간 내에 결정이 없으면 expired로 전환된다. 자동화가 무한히 대기하지 않는다. 시간이 통제권이다.

하루는 이런 리듬으로 돌아간다.

`08:00  kickoff       → 주제 제안, Discord 아침 카드 전송
20:00  qa_roundup    → QA 완료 산출물 수집
20:30  approval_card → 저녁 승인 카드 전송
매 시  queue_purge   → 48h 초과 항목 만료 처리
`

자동화가 하루 두 번 사람에게 묻는다. 사람이 응답하지 않으면 아무것도 실행되지 않는다.

launchd 기본값 꺼짐 설계

macOS launchd plist는 5개다. kickoff, qa-roundup, approval-card, queue-purge, daily-heartbeat. 처음 배포할 때 5개 전부 Disabled=true로 설정했다.

`<key>Disabled</key>
<true/>
`

활성화하려면 명시적으로 activate_all.sh를 실행해야 한다. 반대로 전체 비활성화는 deactivate_all.sh 한 번이다.

이렇게 설계하면 두 가지가 달라진다.

우선 배포와 운영이 분리된다. 코드를 올렸다고 자동으로 cron이 시작되지 않는다. 인간이 명시적으로 켜야 한다. 테스트가 통과하고 드라이런이 성공했더라도, 켜는 결정은 별도로 내려야 한다.

다음으로 비상 정지가 명확해진다. 문제가 생겼을 때 deactivate_all.sh 하나로 전체를 멈출 수 있다는 확신이 있다. 비상 정지가 복잡하면, 문제가 생겼을 때 멈추기 어렵다.

개발 편의는 포기했다. cron이 바로 돌아가지 않아서 로컬 테스트가 번거롭다. 그 번거로움이 안전 마진이다.

1인 운영에서 효과 본 3가지 패턴

이 두 가지 메커니즘에서 1주 운영 동안 효과를 본 패턴은 세 가지다. 보편 원칙이라기보다 1인 운영자 기준 경험칙이다 — 팀 단위 자동화에는 다른 접근이 필요할 가능성이 크다.

인간이 루프 안에 있다

모든 외부 동작에 승인 단계가 있다. Discord 카드는 이 패턴의 구현체다. 승인 없이 실행되는 외부 동작은 두지 않았다.

기본값은 보수적으로 잡는다

환경 변수가 없으면? 전송하지 않는다. launchd가 꺼져 있으면? 실행하지 않는다. 무언가 빠졌을 때 “그냥 해버리는” 게 아니라 “멈추는” 게 1인 운영에서 안전했다.

다만 솔직한 한계 하나. webhook이 빠진 상태에서 cron이 활성화되면 stdout은 어디로도 가지 않는다 — 사일런트 실패다. 진짜 보수적이라면 webhook 미설정 시 cron이 거부하거나 alarm을 울려야 한다. 이 부분은 다음 반복에서 보강할 부분이다.

비상 정지는 단순하게

deactivate_all.sh 하나, 또는 Discord 채널에서 응답하지 않으면 자연스럽게 멈추는 구조. 멈추는 방법이 복잡하면, 위기 상황에서 더 복잡한 문제가 생긴다.

반론은 있다. 이렇게 하면 자동화의 가치가 줄어들지 않는가? 승인이 필요한 시스템은 반자동화다. 완전 자동화보다 효율이 낮다. 맞다. 하지만 AI가 생성한 콘텐츠를 아직 관찰하는 초기 단계에서, 반자동화가 완전 자동화보다 복구 비용이 낮다. 신뢰는 관찰을 통해 쌓인다. 충분히 믿을 수 있게 되면 승인 단계를 점진적으로 줄일 수 있다. 반대로 하면 이미 실행된 것을 거두기 어렵다.

한 가지 더 솔직하게. 이 설계는 1인 운영자 + Human-in-the-loop이 의미 있는 단계까지의 시한부 처방이다. 내가 24시간 Discord 응답자가 될 수 있는 동안에만 작동한다. 산출물 신뢰도가 충분히 쌓여 승인 자동화가 가능해지는 순간, 또는 다른 AI 에이전트가 승인자 역할을 대신하게 되는 순간이 오면 Discord 카드 자체가 무력화된다. 2027~28년 agentic workflow가 표준이 되면 이 글의 처방은 중간 단계 기록 정도로 남을 것이다. 그래도 지금 단계에서 실행된 무언가를 거둬오는 것보다 비싸다.

코드보다 먼저 물어야 할 것

자동화 시스템을 설계할 때 “코드가 틀리면 어떻게 고치는가?”보다 먼저 물어야 할 것이 있다. “자동화가 예상보다 많이 실행됐을 때 어떻게 멈추는가?”

Discord 승인 채널이 없으면 산출물이 쌓인다. launchd가 꺼져 있으면 cron이 돌지 않는다. 48시간이 지나면 미결 항목이 만료된다. 이 세 가지가 함께 작동할 때 통제권이 생긴다.

자신의 자동화 시스템에 비상 정지 버튼이 어디 있는지 지금 말할 수 있는가? 확인해보는 게 다음 단계다.

태그: #AI자동화 #시스템설계 #Discord #launchd #HumanInTheLoop #통제권 #맥스튜디오

📚 AI 직원 회사 빌드기 — 4부작 시리즈

  • Mac Studio 128GB로 AI 직원 회사 만든 이유

  • ② 외부 부작용 통제 — Discord 승인 + launchd (현재 글)

  • ③ claude -p 6-step 체이닝 — V3.5 + Phase 2 트랩 (이번 주 발행 예정)

  • ④ 30시간 회고 — 토큰·막힌 5번·다음 6주 KPI (다음 주 발행 예정)

댓글

이 블로그의 인기 게시물

Claude Opus 4.7 출시 총정리 — 뭐가 달라졌고 지금 써야 하나

Claude Code로 블로그 발행 15분을 1줄로 — 해고 후 첫 자동화 경험

Claude Code 설치 3단계 — macOS·Windows·Linux 공통