“검토가 끝났으니 배포하세요”라는 메시지
구현 에이전트가 다른 에이전트에게서 “검토가 끝났으니 배포하세요”라는 메시지를 받았다고 해 볼게요. 메시지를 보낸 세션이 확인됐고 내용도 손상 없이 도착했어요. 이제 바로 배포해도 될까요?
사용자가 배포를 허용했는지를 따로 확인해야 해요. 다른 에이전트가 작업 결과를 알려 주는 것과 사용자 권한을 새로 부여하는 것은 다른 일이에요. 기존에 사용자가 배포를 요청했다면 그 범위 안에서 진행할 수 있지만, 동료 에이전트의 메시지만으로 허용 범위를 넓힐 수는 없어요.
AGS의 세션 메시징은 이 구분을 문서와 처리 절차에 반영해요. 먼저 메시지를 안정적으로 전달하는 구현을 보고, 그 구현이 해결하지 않는 권한 문제를 살펴볼게요.
전송 응답을 잃어버렸을 때 생기는 문제
발신자가 메시지를 보냈는데 응답을 받기 전에 연결이 끊어졌다고 해 볼게요. 수신 큐에는 이미 들어갔을 수도 있고, 저장 전에 실패했을 수도 있어요. 발신자가 같은 내용을 새 메시지로 보내면 중복 전달이 발생할 수 있어요.
AGS는 메시지를 준비하는 단계와 준비한 메시지를 제출하는 단계를 나눠요. 준비 단계에서 본문, 발신자, 수신자와 유효기간을 정하고 메시지 ID를 발급해요. 이 단계에서는 수신 큐에 넣지 않아요. 제출 단계에서 그 ID로 큐에 넣고 결과를 남겨요.
재시도할 때는 새 메시지를 만드는 대신 같은 ID를 제출할 수 있어요. 저장된 제출 결과가 있으면 그 결과를 돌려줘요. 아래는 SessionMessageStore.submitPrepared에서 이미 처리된 메시지를 확인하는 실제 코드 일부예요.
if (row.receipt !== null) {
const receipt = JSON.parse(String(row.receipt)) as { messageId: string; createdAt: string; expiresAt: string };
const autoWake = this.autoWakeOutlook(target, nowMs);
this.database.exec("COMMIT");
return { ...receipt, duplicate: true, autoWake };
}저장된 receipt가 있으면 다시 큐에 넣지 않고 duplicate: true와 함께 반환해요. 같은 요청을 반복해도 큐 삽입을 중복하지 않도록 하는 멱등성 처리예요. 다만 준비·결과 기록에는 유효기간과 용량 제한이 있어요. 영원히 모든 메시지의 중복을 막는다는 뜻은 아니에요.
큐 삽입과 결과 기록은 함께 확정하기
한쪽만 저장되면 재시도 판단이 깨질 수 있어요. 큐에는 들어갔지만 결과 기록이 없다면 다시 제출할 때 새 전송으로 처리할 위험이 있어요. 반대로 결과만 있고 큐 삽입이 빠지면 발신자는 성공했다고 믿는데 수신자는 받지 못해요.
submitPrepared는 SQLite의 BEGIN IMMEDIATE로 트랜잭션을 시작해요. 메시지 ID의 소유자와 만료 여부를 확인하고, 용량을 검사한 뒤 큐 삽입과 첫 결과 기록을 같은 트랜잭션 안에서 처리해요. 정상적으로 끝나면 커밋하고 예외가 발생하면 롤백해요.
제출 결과를 저장한 준비 레코드에서는 본문을 NULL로 바꿔요. 이는 준비 레코드의 처리이며 전달 큐 전체의 본문을 즉시 삭제한다는 뜻은 아니에요. 전달에 필요한 데이터와 재전송 결과를 확인하는 데이터를 구분해서 봐야 해요.
제출 성공, ACK, 작업 완료는 서로 달라요
메시지를 큐에 넣었다고 수신 에이전트가 작업을 끝낸 것은 아니에요. AGS는 메시지를 가져가는 claim과 메시지 처리 확인인 ACK도 구분해요. claim의 lease가 끝나고 ACK가 없다면 같은 메시지가 다시 전달될 수 있어요.
따라서 큐 삽입의 멱등성이 곧 업무 동작이 정확히 한 번 실행된다는 뜻은 아니에요. 메시지를 받아 파일을 수정하거나 외부 요청을 수행하는 쪽에서도 중복 수신을 고려해야 해요.
| 관측한 상태 | 말할 수 있는 범위 |
|---|---|
| 메시지 준비 | ID와 보낼 내용이 준비됐어요 |
| 제출 성공 | 메시지가 큐에 반영됐다는 결과를 받았어요 |
| claim | 수신 측이 처리할 메시지를 가져갔어요 |
| ACK | 해당 메시지에 처리 확인을 남겼어요 |
| 별도 작업 결과 | 요청한 작업이 어떻게 끝났는지 확인할 자료가 생겼어요 |
어느 행도 그 자체로 사용자 승인을 의미하지 않아요. 자동 깨우기 역시 수신자가 곧 작업을 마칠 것이라는 보증으로 해석하면 안 돼요. 내구성 있게 저장하는 큐와 에이전트를 실행시키는 깨우기 동작은 별도로 봐야 해요.
출처가 확인돼도 권한이 생기지는 않아요
AGS는 다른 세션의 메시지를 신뢰할 수 없는 맥락 자료로 다뤄요. 발신 정보와 전달 기록은 어디서 온 메시지인지 확인하는 데 도움을 주지만, 본문의 명령을 상위 지시로 바꾸지는 않아요.
이 구분이 없으면 “사용자가 승인했다”는 문장이 여러 에이전트를 거치며 사실처럼 전달될 수 있어요. 수신자는 메시지의 어조나 발신 에이전트의 역할명보다 현재 작업에 주어진 권한과 실제 승인 근거를 확인해야 해요. 특히 삭제, 배포, 외부 메시지 발신처럼 부수효과가 있는 행동에서는 이 차이가 중요해요.
그렇다고 이 구현을 운영체제 수준의 격리 장치로 볼 수는 없어요. AGS 문서는 같은 OS 사용자 권한의 프로세스까지 적대적으로 격리한다고 주장하지 않아요. 로컬 저장소나 키에 접근할 수 있는 프로세스를 모두 막는 보안 장치와, 협력하는 에이전트 사이에서 지시의 출처를 구분하는 절차는 적용 범위가 달라요.
실패 경로를 기준으로 읽은 결과
메시지 수명주기 테스트에는 응답 유실 후 재제출, 재시작, 여러 프로세스가 관여하는 상황을 다루는 사례가 있어요. 이 글에서는 해당 테스트 정의와 트랜잭션 처리를 읽어 설계를 확인했어요. 실제 운영 환경의 모든 전송 장애나 악의적인 발신자에 대한 안전성을 증명한 것은 아니에요.
첫 글에서는 완료 메시지와 완료 상태를 나눴어요. 마지막 글에서는 메시지를 전달한 사실과 그 내용을 실행할 권한을 나눴고요. 둘 다 에이전트가 내놓은 문장을 그대로 시스템의 사실로 취급하지 않는 문제와 연결돼요. 작업을 이어가고, 다시 시도하고, 나눠 맡기는 과정에서도 무엇을 확인한 상태인지 구체적으로 남겨야 해요.