독립 수집기로 구성한 아침 브리핑
아침마다 주가와 뉴스, 메일, 일정을 각각 확인하는 대신 한 번에 모아 보고 싶었습니다. OpenClaw 설정을 마친 뒤 처음 연결한 작업도 이 아침 브리핑이었습니다.
처음에는 주가, 기술 뉴스, 외부 서비스 상태를 읽는 수집기 세 개로 시작했습니다. 이후 메일과 일정, 게임 공지를 추가했습니다. 수집 대상이 늘어나는 동안에도 각 수집기는 따로 실행할 수 있게 두고, 결과를 모아 요약하고 전송하는 부분을 연결했습니다.
실행 환경 분리
데이터 출처마다 필요한 라이브러리와 인증 방식이 다릅니다. 이를 하나의 Python 환경에 모두 넣기보다, 각 수집기가 자신의 가상환경에서 실행되도록 했습니다. 통합 스크립트는 subprocess로 수집기를 실행한 뒤 결과를 모읍니다.
| 수집 대상 | 가져오는 내용 |
|---|---|
| 주가 | 관심 종목의 가격과 변동 |
| 기술 뉴스 | RSS와 기술 뉴스 링크 |
| 외부 서비스 | 서비스 상태와 장애 공지 |
| 메일과 일정 | |
| 게임 공지 | 새 공지의 본문과 링크 |
아래 코드는 이 실행 방식을 단순화한 예시입니다. 실제 경로나 제한 시간을 그대로 옮긴 코드는 아닙니다.
import subprocess
result = subprocess.run(
["/path/to/collector/.venv/bin/python", "/path/to/collector/main.py"],
capture_output=True,
text=True,
check=True,
timeout=60,
)
collected_text = result.stdout이렇게 하면 통합 스크립트의 Python 환경에 수집기 라이브러리를 설치하지 않아도 됩니다. 어느 수집기의 실행 파일과 환경을 사용하는지도 호출하는 곳에서 확인할 수 있습니다.
메일과 일정은 처음에 osascript로 가져왔지만, 이후 OAuth와 REST API를 사용하는 방식으로 바꿨습니다. 데스크톱 앱 상태에 의존하지 않고 데이터를 읽기 위해서입니다. 인증 토큰은 코드와 분리해 관리했습니다.
한 수집기의 실패와 전체 실행 결과
수집기를 별도 프로세스로 실행해도 통합 스크립트가 첫 예외에서 끝나면 나머지 자료를 모을 수 없습니다. 어떤 자료가 빠져도 브리핑을 만들지, 꼭 필요한 자료가 없으면 멈출지를 호출하는 쪽에서 정해야 합니다.
다음은 한 수집기의 성공·실패를 결과로 돌려주는 예시입니다. 실제 운영 스크립트를 그대로 옮긴 것은 아니며, 앞의 subprocess.run 예제에 오류 처리를 붙였습니다.
import subprocess
def collect(name, command, timeout=60):
try:
result = subprocess.run(
command, capture_output=True, text=True,
encoding="utf-8", timeout=timeout, check=True,
)
except subprocess.TimeoutExpired:
return {"name": name, "status": "timeout", "text": None}
except subprocess.CalledProcessError as error:
return {"name": name, "status": "failed",
"exit_code": error.returncode, "text": None}
text = result.stdout.strip()
return {"name": name, "status": "ok" if text else "empty", "text": text}timeout, failed, empty를 나눈 것은 빈 자료의 의미가 다르기 때문입니다. 새 일정이 없다는 정상 결과와 일정 API 인증이 실패한 결과를 같은 빈 문자열로 넘기면 모델이 두 상태를 구분할 수 없습니다. 여기서는 오류 원문을 결과에 넣지 않았습니다. 인증 응답이나 메일 본문까지 최종 메시지에 섞이지 않게 상세 로그와 요약 입력을 나누기 위해서입니다.
실제 수집기는 “조회는 성공했지만 항목이 없음”도 별도 상태로 반환하는 편이 좋습니다. 통합 스크립트는 이 상태를 모아 일정 조회 실패처럼 빠진 자료를 표시하거나 필수 항목이면 요약을 중단할 수 있습니다. 빠진 항목을 모델이 추측해서 채우는 방식으로 처리하지 않습니다.
launchd 등록 예시
정기 실행에는 로그인한 터미널의 현재 경로와 가상환경을 기대할 수 없습니다. 실행 파일과 작업 경로를 명시하면 터미널에서는 되는데 예약 실행에서는 실패하는 차이를 줄일 수 있습니다.
아래는 사용자 LaunchAgent의 구성 예시입니다. USERNAME과 파일 경로, 실행 시간은 환경에 맞게 바꿔야 합니다. 실제 다섯 작업의 plist를 복사한 것은 아닙니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.example.briefing</string>
<key>ProgramArguments</key><array>
<string>/Users/USERNAME/briefing/.venv/bin/python</string>
<string>/Users/USERNAME/briefing/main.py</string>
</array>
<key>WorkingDirectory</key><string>/Users/USERNAME/briefing</string>
<key>StartCalendarInterval</key><dict>
<key>Hour</key><integer>8</integer>
<key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key><string>/Users/USERNAME/briefing/run.log</string>
<key>StandardErrorPath</key><string>/Users/USERNAME/briefing/error.log</string>
</dict></plist>등록 전에 plutil -lint로 plist 문법을 보고, 등록 후에는 launchctl print gui/$(id -u)/com.example.briefing으로 사용자 작업 상태를 확인할 수 있습니다. 로그 디렉터리는 미리 준비해야 합니다. 일정은 장비의 시간대와 절전 상태에도 영향을 받으므로 크론처럼 매번 정확히 깨워 실행하는 것으로 가정하지 않았습니다. launchd의 작업 구성은 아래 Apple 문서를 함께 참고했습니다.
수집 결과 요약과 Discord 전송
수집한 결과는 모델에 전달해 항목별 메시지로 정리합니다. 초기에는 Gemini를 사용했습니다. 생성된 메시지는 Discord로 보내고, 요약에 사용한 자료와 최종 메시지는 따로 남겼습니다.
입력과 결과를 보관한 이유는 브리핑 내용에 문제가 있을 때 확인할 곳을 나누기 위해서입니다. 원본에 없던 내용이 요약에 들어갔는지, 처음부터 오래된 자료가 수집됐는지를 구분할 수 있어야 했습니다. 메시지가 오지 않는 경우에도 수집 결과부터 다시 만드는 대신 요약과 전송 로그를 이어서 볼 수 있습니다.
OpenClaw의 중복 요약 문제
정기 실행을 OpenClaw에 연결했을 때는 이미 생성된 메시지가 모델을 한 번 더 거치면서 형식이 바뀌었습니다. 수집한 자료로 브리핑을 완성했는데, 전송 과정에서 다시 요약되고 있었습니다.
이 작업에서 필요한 것은 완성된 메시지를 정해진 시간에 보내는 것이었습니다. 그래서 실행과 전송을 별도 스크립트로 나누고 macOS의 launchd에 등록했습니다. 3월 초에는 주요 작업 다섯 개를 plist로 관리했습니다.
OpenClaw는 대화형 작업에 사용하고, 정해진 순서로 반복 실행하는 작업은 등록한 스크립트가 맡도록 구분했습니다. 브리핑 전송 단계에서는 모델을 다시 호출하지 않고 생성된 결과를 사용합니다.
운영 중 추가한 처리
이렇게 수집기를 따로 실행하고 결과를 모아 요약·전송하는 구성으로 정기 브리핑을 돌리기 시작했습니다. 문제가 생기면 전체를 다시 수집하기보다 실패한 단계와 남아 있는 결과부터 확인할 수 있었습니다.
정기 실행을 붙인 뒤에도 인증 오류와 전송 실패, 오래된 시세와 반복 뉴스는 따로 고쳐야 했습니다. 수집·요약·전송을 나눈 구조는 유지하고, 각 단계에서 생긴 문제를 후속 작업으로 보완했습니다.
후속 변경은 아래 글에서 다룹니다.