우리 야간조는 모델 부대다
기획자는 계획을 짜고, 일꾼은 실행하고, 검증자는 공격한다. 그리고 화이트리스트와 장부를 쥔 게이트 하나가 이들 전부와 바깥세상 사이를 막고 선다.
3 분 읽기서울과 나트랑에서 씁니다
어느 순간 예약 작업과 모니터, 파이프라인의 수가 사람이 교통정리를 감당할 수 있는 선을 넘어버렸다. 우리 해법은 거대한 자율 에이전트 하나가 아니었다. 결정론적 스크립트가 목줄을 쥔, 작은 에이전트 한 무리였다.
제어 흐름은 코드가, 내용은 모델이 쥔다
오케스트레이터는 평범한 코드다. 무엇을 어떤 순서로 얼마나 동시에 돌릴지, 실패하면 어떻게 할지를 코드가 정한다. 모델은 그 사이 단계를 채운다. 기획자는 목표를 작업 목록으로 바꾼다. 일꾼은 스텝 예산이 걸린 도구 샌드박스 안에서 작업을 실행한다. 검증자는 돕는 게 아니라 반박하라는 프롬프트를 받고 일꾼의 결과물을 공격한다. 보고자는 살아남은 결과를 사람이 읽을 수 있게 압축한다. 모든 역할이 JSONL 트랜스크립트를 남기니, 어느 날 밤의 작업이든 다음 날 아침에 한 단계씩 되짚어 재생할 수 있다.
구조상 딱 하나의 규칙이 있다. 모델끼리는 절대 직접 대화하지 않는다. 반드시 오케스트레이터를 거쳐 말하고, 그래서 모든 홉이 로그로 남고 예산이 걸리고 언제든 끊을 수 있다.
위험한 방향은 바깥으로 나가는 쪽이다
운영 자동화에서 위험한 쪽은 읽기가 아니라 쓰기다. 새벽 4시에 사람에게 메시지를 보낼 수 있는 봇은, 언젠가 새벽 4시에 마흔 번을 보낸다. 우리 무리에서 바깥으로 나가는 경로는 사람에게 가든 API로 가든 전부 게이트 하나를 지난다.
function send(msg, to) {
if (!WHITELIST.has(to)) return refuse('recipient');
if (ledger.countToday(to) >= DAILY_CAP) return refuse('cap');
ledger.append({ to, digest(msg), at: now() }); // written BEFORE sending
return transport.send(msg, to);
}
수신자 화이트리스트, 코드로 강제하는 하루 상한, 보내기 전에 먼저 쓰는 장부, 그리고 모든 발신자가 함께 쓰는 게이트 하나. 게이트 하나를 공유하는 스크립트 열 개가, 예의범절을 두고 저마다 다른 열 가지 의견을 가진 스크립트 열 개를 이긴다.
실패 알림보다 침묵 감지
# the watchdog does not listen for errors. it demands proof of life.
find /opt/health -name '*.ok' -mmin +90 -print # stale heartbeat = incident
크론 작업이 요란하게 실패하는 건 동화 속 얘기다. 프로덕션에서는 아예 안 돌면서 실패한다. 스케줄의 오타, 죽은 virtualenv, 작성자가 예상 못 한 타임존으로 맞춰진 서버 시계. 우리 워치독은 하트비트가 오기를 기다리다, 그게 없으면 그 자체를 경보로 친다. 이 발상을 뒤집은 뒤로, 뭔가 일주일 내내 조용히 죽어 있던 부류의 사고는 그냥 사라졌다.
우리가 정착한 경험칙은 이렇다. 자동화는 그 폭발 반경이 얼마나 기계적으로 틀어막혀 있느냐에 비례해 자율성을 얻는다. 게이트를 먼저 짜고, 그다음에 무리를 풀어라.