나의 퍼블리싱 에이전트는 하루에 두 번 실행되며 아무것도 기억하지 못한다. 그것은 버그가 아니라 설계다.
요약
상태를 공유하지 않는 독립적인 컨테이너 기반 에이전트 설계 방식을 소개합니다. 로컬 카운터 대신 외부 API를 유일한 진실의 원천(Single Source of Truth)으로 활용하여 상태 불일치 문제를 해결하는 전략을 다룹니다.
핵심 포인트
- 상태를 공유하지 않는 독립적 실행 환경 설계
- 외부 API를 통한 상태 동기화 및 진실의 원천 확보
- 로컬 상태 관리 시 발생할 수 있는 오류 및 데이터 오염 방지
- 네트워크 차단 등 예외 상황에서의 견고한 에이전트 동작 보장
나는 하루에 두 번, 실행 사이에 인간의 개입 없이 나를 대신해 DEV.to 기사를 작성하고 게시하는 예약된 작업(scheduled job)을 가지고 있습니다. 이를 구동하는 프롬프트에는 실제로 구현해 보기 전까지는 사소해 보이는 규칙이 하나 있습니다: 실행당 1~3개의 기사를 게시하되, 오늘의 총 게시물이 5개를 넘지 않도록 하며, 만약 하루의 두 번째 실행이 첫 번째 실행에서 게시된 것이 없음을 발견하면 최소 2개를 게시해야 한다는 규칙입니다.
까다로운 부분은 산술적인 계산이 아닙니다. 이 숫자에 대해 협력해야 하는 두 번의 실행이 프로세스(process), 변수(variable), 데이터베이스 행(database row), 심지어 파일 시스템(filesystem)조차 공유하지 않는다는 점입니다. 각 실행은 완료 후 회수되는 새로운 컨테이너(container)입니다. 실행 A는 실행 B가 실제로 확인할 만한 어딘가에 기록을 남기는 방법 외에는 실행 B에게 메모를 남길 방법이 없습니다.
그렇다면 이 카운트(count)는 어디에서 오는 걸까요? 내가 제어하는 그 어떤 것에서도 오지 않습니다. 두 실행이 모두 볼 수 있는 단 한 가지, 즉 DEV.to 자체의 API에서 옵니다.
import json, os, urllib.request
key = os.environ["DEV_TO_API"]
...
이것이 서로에 대해 아무것도 모르는 에이전트의 두 실행 사이의 유일한 조정 메커니즘(coordination mechanism)입니다. 락 파일(lock file)도 없고, 내가 증가시키는 카운터(counter)도 없으며, 레포지토리(repo)에 커밋된 last_run.json도 없습니다. 원격 시스템(remote system)이 유일한 진실의 원천(single source of truth)이며, 모든 실행은 시스템에 물어봄으로써 처음부터 상태(state)를 다시 도출합니다.
이것은 사람들이 자율 에이전트(autonomous agents)에 대해 보통 걱정하는 실패 모드(failure mode)와는 다릅니다. 보통의 걱정은 "잘못된 일을 할 것인가"입니다. 여기서 더 조용한 위험은 "서로를 모르는 두 존재가 모두 자신이 유일한 책임자라고 가정할 것인가"입니다. 로컬 카운터(파일, 컨테이너 이미지에 포함된 환경 변수, 다음 실행까지 유지되지 않는 그 무엇이든)를 유지하는 크론 잡(cron job)은 컨테이너가 재빌드되거나 실행이 중간에 실패하는 순간 오차가 발생할 것입니다. 로컬 숫자가 다른 실행의 기록을 전혀 보지 못하기 때문에, 이미 5개가 게시된 날에 기사를 3개 더 게시하는 일을 아무렇지 않게 수행할 것입니다.
대신 API로부터 카운트를 도출하는 것은 상태(state)가 진정으로 어긋날 수 없음을 의미합니다. 왜냐하면 상태의 복사본은 단 하나뿐이며, 실제 제약 조건(로컬 카운터가 무엇이라 말하든 하루에 8개의 게시물을 원하는 사람은 아무도 없습니다)을 강제하는 서버에 존재하기 때문입니다. 두 번의 실행은 서로 협력하는 것이 아닙니다. 둘 다 독립적으로 동일한 심판에게 권한을 위임할 뿐입니다.
저는 2026-07-14일에 이 방식의 날카로운 이점을 경험했고, 개인 작업 노트에 다음과 같이 기록했습니다. 그날의 첫 번째 실행은 할당량(quota) 확인 단계에 도달하기도 전에 dev.to:443을 차단하는 샌드박스 송신 정책(sandbox egress policy)에 의해 완전히 차단되었습니다. 해당 실행은 아무것도 게시하지 않았고, 똑같이 중요한 점은 아무것도 소비하지 않았다는 것입니다. 애초에 실수로 감소시키거나 손상시킬 로컬 상태(local state)가 없었기 때문입니다. 그날 이후 송신이 재개되었을 때, 다음 실행은 API에 새로 요청하여 게시된 것이 0개임을 확인했고, 정상적인 1~3개의 범위를 사용했습니다. 자신의 카운트를 로컬에서 추적하는 실행이었다면, 실패한 실행이 "카운트되는지" (게시하기 전인가 후인가?)에 대해 추론해야 했을 것이며, 이는 상태가 실제 제약 조건이 있는 곳이 아닌 다른 곳에 존재할 때 스며드는 전형적인 장부 관리 버그(bookkeeping bug)입니다.
이 설계가 강제하는 또 다른 점은 결과물을 누가 검토할 것인가에 대한 결정인데, 검토자가 없기 때문입니다. 인간 참여형(human-in-the-loop) 파이프라인은 평범한 기사를 통과시킨 뒤 검토 단계에서 잡아낼 수 있습니다. 하지만 제 방식은 그럴 수 없습니다. 누군가 읽을 때쯤이면 이미 제 이름으로 라이브 상태가 되어 있기 때문입니다. 그래서 프롬프트에는 명시적인 하한선이 포함되어 있습니다: "트렌딩 토픽을 스캔한 후 진정으로 독특한 관점을 가진 강력한 트렌드가 없다면, 약한 기사를 억지로 만들기보다 기사를 적게, 최소 1개만 게시하라." 이 문구는 할당량 확인(quota check)과 토픽 중복 제거(topic-dedup) 확인이 단 한 단어가 작성되기 전에 모두 실행되기 때문에 작동합니다. 에이전트는 사후에 자신의 숙제를 채점하도록 요구받는 것이 아니라, 채점할 숙제 자체가 적어지도록 사전에 제약을 받는 것입니다.
구체적으로, 아무것도 작성하기 전에 이 실행(run)은 약 25개의 이전 포스트 작업 로그(docs/project_notes/issues.md)와 후보 주제들을 비교(diff)합니다. 따라서 같은 날 두 번 실행되더라도, 동일한 트렌드 목록에서 독립적으로 같은 아이디어에 도달할 수 없습니다. 이는 할당량 확인(quota check)과 동일한 패턴입니다. "내가 이걸 전에 했었나"라는 메모리 상의 감각을 믿지 말고, 실제 기록을 다시 읽으십시오.
만약 제가 이것을 처음부터 다시 만든다면, 에이전트가 실제보다 더 똑똑해 보이게 만들고 싶은 충동을 억제할 것입니다. 로컬에서 일일 횟수를 추적하기 위해 작은 SQLite 파일을 추가하는 것은 쉬운 일이었겠지만, 결과적으로는 엄격하게 더 나쁜 선택이었을 것입니다. 컨테이너 재빌드(rebuild) 시 살아남아야 하는 상태(state)가 하나 더 늘어나고, 현실과 조용히 어긋날 수 있는 요소가 하나 더 늘어나며, "카운터에는 3이라고 되어 있는데 실제 라이브 포스트는 5개입니다"로 시작하는 버그 리포트가 하나 더 늘어나는 셈이기 때문입니다. 매번 API에 물어보고 답변을 절대 캐싱(cache)하지 않는 지루한 방식이 당신에게 거짓말을 할 수 없는 방식입니다. 애초에 아무것도 기억하지 않기 때문입니다.
이 교훈의 일반적인 형태는 다음과 같습니다: 에이전트의 두 호출(invocation)이 특정 수치에 대해 합의해야 할 때, 그들이 서로 속삭일 수 있는 사이드 채널(side channel)을 만들지 마십시오. 두 호출 모두를 이미 진실(ground truth)을 소유하고 있는 시스템으로 향하게 하고, 그 어느 쪽도 자신의 메모리를 신뢰하지 않도록 하십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기