맥 메뉴바로 내 AI 에이전트 상태 모니터링하기 (파이썬 rumps)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱

launchd 봇 모니터링이 필요해진 건 봇이 하나둘 늘면서부터였습니다. 유튜브 아카이브 봇, 인스타 정리 봇, 뉴스 큐레이션 봇... macOS launchd로 백그라운드에 띄워두고 쓰는데, 문제는 죽어도 아무 말이 없다는 것입니다. 며칠 뒤에야 "어, 이 봇 안 돌고 있었네" 하고 발견하면 그사이 데이터가 통째로 빠집니다. 이 글은 그걸 막으려고 만든 메뉴바 앱입니다. 메뉴바에 4/4 한 줄로 "지금 몇 개가 살아 있는지"가 항상 보이고, 클릭하면 봇별 상태·로그·재시작까지 됩니다.
직전 편 Claude Code 사용량 메뉴바 앱과 같은 "메뉴바에서 한눈에" 계열인데, 이번엔 대상이 내가 돌리는 자동화 봇들입니다.
시리즈 · 직접 만든 macOS 도구 — 메뉴바에 사는 작은 앱들을 하나씩 만들어 온 기록입니다.
- Claude Code 사용량 메뉴바 모니터
- AI 에이전트 상태 모니터 (파이썬 rumps) — 지금 보는 글
- 맥북 배터리 건강·메뉴바 유틸 3선
- 말하면 노션에 꽂히는 온디바이스 받아쓰기 앱
- 맥 절전·자리비움 방지 (마우스 지글러)
1. 왜 필요한가 — launchd 봇은 죽어도 조용하다
launchd로 봇을 띄우면 편합니다. 로그인하면 자동 실행되고, 꺼져도 다시 살아납니다. 그런데 바로 그 "알아서 돌아간다"가 함정입니다.
- 봇이 에러로 죽으면 알림이 없습니다. 터미널을 안 보면 모릅니다.
- 살았는지 확인하려면 매번
launchctl list | grep ...을 쳐야 합니다. - 봇이 3개, 5개로 늘면 어느 게 죽었는지 한눈에 안 보입니다.
결국 "다 잘 돌고 있겠지" 하고 방치하다가, 며칠치 데이터가 빠진 걸 뒤늦게 발견합니다. 필요한 건 단순합니다. 메뉴바에 "지금 몇 개 살아 있나"가 늘 떠 있는 것. 빨간불이 뜨면 그때 들여다보면 됩니다.
2. 구조 — 30초마다 launchctl을 읽는다
복잡할 것 없습니다. 핵심은 세 가지입니다.
[1] 30초마다 launchctl list <label> 실행
→ 각 봇의 PID / 마지막 종료 코드 확인
[2] PID 있으면 = 살아있음(running), 없으면 = 죽음(stopped)
[3] 메뉴바 타이틀에 "살아있는 수 / 전체 수" 표시
정상: 4/4 · 하나 죽음: !3/4

메뉴바 앱은 rumps(파이썬 메뉴바 앱 라이브러리)로 만듭니다. Dock에는 아이콘이 안 뜨고(메뉴바 전용), 백그라운드에서 30초마다 상태를 갱신합니다. 봇 목록은 코드에 라벨 + 로그 경로로 등록해둡니다.
드롭다운에서 각 봇 오른쪽의 ">"를 누르면 그 봇만을 위한 서브메뉴가 열립니다. 터미널을 따로 열거나 launchctl 명령을 외워 칠 필요 없이, 여기서 바로 조작합니다.
- 로그 보기(Terminal) — 해당 봇의 로그를
tail -f로 터미널에 띄워 실시간으로 확인합니다. - 시작 / 중지 — launchd 작업을
launchctl로 켜고 끕니다. - 재시작 — 죽었거나 멈춘 봇을 한 번에 다시 띄웁니다(중지 후 시작).
덕분에 메뉴바 빨간불 확인 → 클릭 → 재시작까지, 키보드 없이 몇 초 만에 끝납니다. 어느 봇이 죽었는지 찾아 명령어를 치던 과정이 통째로 사라집니다.
3. 단계별 구현
3.1 모니터할 봇 목록 정의
각 봇의 launchd 라벨과 로그 경로를 등록합니다.
AGENTS = [
{"id": "1", "name": "YouTube", "label": "com.youtube-pipeline.discord-bot",
"log": "~/.youtube-pipeline/logs/discord_bot.log"},
{"id": "2", "name": "Insta", "label": "com.insta-notion-pipeline.discord-bot",
"log": "~/.insta-notion-pipeline/logs/discord_bot.log"},
# ... 실제 쓰는 봇만 등록
]
3.2 상태 조회 — launchctl 파싱
launchctl list 출력에서 PID와 마지막 종료 코드를 뽑습니다.
import subprocess
def get_status(label):
r = subprocess.run(["launchctl", "list", label],
capture_output=True, text=True, timeout=5)
if r.returncode != 0:
return None # 라벨 자체가 없음
pid = None
for line in r.stdout.splitlines():
if '"PID"' in line:
pid = line.split("=")[-1].strip().rstrip(";")
return pid # PID 있으면 살아있음
3.3 메뉴바 타이틀 — 숫자로 최소 표시
rumps로 메뉴바에 "살아있는 수 / 전체"를 띄웁니다. 이모지 대신 숫자만 써서 폭을 최소화합니다(이유는 4장에서).
running = sum(1 for a in AGENTS if get_status(a["label"]))
total = len(AGENTS)
self.title = f"{running}/{total}" if running == total else f"!{running}/{total}"
3.4 30초 자동 갱신 + 클릭 메뉴
rumps 타이머로 30초마다 위 갱신을 돌리고, 메뉴에는 봇별 항목(상태 텍스트)과 "로그 보기 / 재시작 / 중지" 콜백을 붙입니다. 로그 보기는 tail -f를 터미널에 띄우면 실시간으로 흐릅니다.
3.5 메뉴바 전용 + 로그인 자동 실행
py2app으로 .app을 만들고, Info.plist에 LSUIElement=1을 넣으면 Dock 없이 메뉴바에만 상주합니다. 로그인 항목에 추가하면 부팅 때 자동으로 뜹니다.
4. 자주 겪는 문제 (실제로 겪고 고친 것)
만들고 나서 정작 안 쓰게 됐던 경험이 있습니다. 그 원인을 직접 디버깅하며 고친 4가지입니다.
4.1 안 쓰는 봇이 죽어 있어 항상 경고가 뜬다
가장 컸던 문제입니다. 예전에 쓰던 봇 2개를 목록에 남겨뒀는데, 그게 죽은 채로 있으니 메뉴바에 항상 ⚠️ 4/6이 떴습니다. 늘 경고가 떠 있으니 "또 빨간불이네" 하고 무뎌져서, 결국 앱 자체를 신뢰하지 않게 됐습니다.
해결은 단순했습니다. 모니터 대상을 "지금 실제로 쓰는 봇"만 남기기. 안 쓰는 2개를 목록에서 빼니 4/4로 깔끔해졌고, 빨간불이 뜨면 그제야 "진짜 문제"라는 신뢰가 생겼습니다. 모니터링 도구는 평소에 조용해야 경고가 의미를 가집니다.
4.2 이모지 아이콘이 커서 메뉴바에서 안 보인다
처음엔 상태를 ✅ 4/4 / ⚠️ 3/4처럼 이모지로 표시했습니다. 그런데 이모지는 폭을 많이 차지해서, 메뉴바가 꽉 찬 맥북(노치 모델)에서는 노치 뒤로 밀려 아예 안 보였습니다.
이모지를 빼고 4/4 숫자만 남기니 폭이 절반 이하로 줄어 잘 보였습니다. 상태 구분은 정상 4/4, 문제 !3/4처럼 기호 한 글자로 충분합니다.
4.3 텍스트 왼쪽에 이상한 빈 공간이 생긴다
rumps 앱을 빈 문자열로 시작(App(""))하면, 라이브러리가 비어 있는 아이콘 자리를 예약해 텍스트 왼쪽에 공백이 생깁니다. 처음부터 타이틀을 지정(App("…"))해서 만들면 그 빈 슬롯이 사라집니다.
4.4 메뉴바가 꽉 차서 안 보인다
앱이 정상이어도 메뉴바 아이콘이 너무 많으면 안 보입니다. ⌘를 누른 채 아이콘을 드래그해 위치를 옮기거나, 메뉴바 관리 앱(Ice 등)으로 안 쓰는 아이콘을 숨기면 됩니다.
마치며
봇을 백그라운드에 띄워두는 순간, "잘 돌고 있겠지"라는 막연한 믿음이 생깁니다. 메뉴바에 4/4가 떠 있으면 그 믿음이 눈으로 확인되는 사실이 됩니다. 빨간불이 뜰 때만 들여다보면 되니, 봇이 늘어도 관리 부담이 늘지 않습니다.
이 앱은 Claude Code 사용량 메뉴바 앱과 같은 "메뉴바에서 한눈에" 도구 묶음의 하나입니다. 소스 코드는 GitHub 저장소에서 확인하실 수 있습니다.
같은 주제 다른 글 — Mac 메뉴바·시스템 유틸
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기