세션이 자기 권한보다 오래 살 수 있어
채팅을 따라다니는 어댑터는 office의 자식 프로세스야. 재시작될 수도 있고, 갈아 끼워질 수도 있고, 등록이 끝났다가 같은 채팅이 나중에 다시 등록되면 뒤에 남겨질 수도 있어. 그 내내 채팅의 네이티브 id는 그대로야. 그러니 신원만으로는 지금 어댑터랑 옛 봉투를 아직 쥐고 있는 낡은 어댑터를 구분 못 해. Coop은 바로 그걸 위해 숫자 하나를 더 붙여. 세션의 세대(generation).
펜싱 토큰으로서의 세대
세션 행은 세대 1에서 시작하고, 숫자가 올라가는 곳은 두 군데야. 하나는 register가 은퇴한 세션을 다시 등록할 때야. 새 세대를 시작하면 "fences anything still acting for the old one"이라고, 코드 주석이 이유를 적어 뒀어. 다른 하나는 CLI로 일을 가져간 채팅이 관리되는 등록으로 입양될 때야. 이땐 adopt_take가 세대를 올려.
엔진이 내주는 건 전부 발급될 때의 세대가 찍혀 있어. 디스패치, 바인딩, 아웃박스 봉투. 어댑터가 요청하는 건 전부 세대를 밝혀. 수락, 거절, 보고, 배달 영수증, 상태 관찰. 그리고 수락, 거절, 보고, 노트, 배달은 전부 작은 도우미 _session을 거쳐. 이 도우미는 세대가 현재 것이 아닌 호출을 전부 거절해. "Stale or exited session generation." 상태 관찰도 따로 똑같이 비교하고. 보고는 한 걸음 더 나가서, 바인딩 자체도 이 세션과 이 세대 것이고 아직 활성이어야 해. 아키텍처의 실패 표는 이걸 담백하게 적어. 옛 세대의 보고는 충돌이고, 현재 주인의 상태는 보존돼.
울타리는 양쪽에서 작동해. 어댑터는 자기 세대 봉투만 배달하고("Host queue does not own this envelope"), 재등록하면 어댑터 파일도 새 숫자로 다시 써. 안 그러면 "the runtime would keep refusing the stale one." 엔진이 뜰 때는 cancel_fenced가 대기 봉투를 훑어. 세션의 현재 세대보다 오래된 세대가 찍혀 있으면 전부 취소하고, 이유는 fenced-generation으로 남겨. 그런 행은 절대 보낼 수 없거든. 그냥 두면 영원히 "배달 중"으로 읽히면서 등록을 붙잡고 있어. 안 읽은 답글은 대신 현재 세대로 새 리뷰가 잡혀. 입양만은 일부러 예외야. 아무도 채팅을 안 지켜보던 동안 쌓인 봉투는 새 세대로 넘어가. 여전히 그 채팅 거니까.
이게 Martin Kleppmann의 펜싱 토큰이야. 잠금을 쥔 프로세스는 멈칫할 수 있어. 모르는 새 임대가 끝나고, 깨어나서는 아무 일 없었다는 듯 써. 해법은 권리를 내줄 때마다 커지는 숫자고, 저장소가 그걸 확인해서 더 작은 숫자를 단 쓰기를 거절하는 거야. Coop의 세대가 그 숫자고 엔진이 그 저장소야. 핵심은 확인이 어디에 있느냐야. 낡은 프로세스의 착한 행실이 아니라 저장소에.
세션 둘, 체크아웃 하나
아빠는 저장소 하나에서 세션 여러 개를 서로 다른 항목으로 늘 같이 돌려. 2026-09-30(todo #285, 커밋 43ddac9)에 체크아웃 하나에서 새 Claude 채팅이 READY라고 답하고 멍하니 있었어. 탐색은 6초 만에 찾았는데, prepare가 디스패치를 거절했거든. 3분 전에 #284를 배정받은 Codex 세션이 같은 체크아웃을 쓰기 범위로 쥐고 있었고, 그 실패는 Telegram에만 닿았어. 저장소 하나에 순서대로 띄운 실행은 첫 번째 뒤로 전부 같은 식으로 실패했어.
고친 방법은 규칙을 뒤집는 거였어. 세션 사이에 쓰기 범위가 겹치면 거절 말고 공개. 수락할 때 shared_checkout이 다른 세션을 훑어. 살아 있는 디스패치의 쓰기 범위가 이번 것과 겹치면(어느 한쪽 경로가 다른 쪽 안에 들어가면) 전부 나열해. 벤더, 행위자, 상태, 범위, 항목 제목까지. 독스트링이 정책 전부야. "A disclosure, not a lock." 같은 세션 같은 세대의 디스패치는 건너뛰어. 네이티브 주인 하나가 자기 자신과 부딪힐 일은 없으니까. 그리고 같은 항목의 중복 디스패치는 여전히 거절돼. 목록이 비어 있지 않으면 작업 봉투에 이 지시가 붙어.
"Other sessions are working in this same checkout right now (shared_checkout lists them and their tasks): never revert, stash, reset, clean or commit their changes; stage and commit only files you changed, by explicit path; if a file you need is also being changed by another session, ask Dad before touching it."
진짜 보호 장치는 원래 자리에 그대로 있어. 가족의 git 규율, 그리고 실행자가 쥐는 잠금. Firelink의 배포 잠금 같은 것.
READY에서 하염없이 기다리는 채팅은 없어
같은 커밋이 새 봉투 종류를 하나 들였어. notice. 채팅이 연결된 뒤에 배정이 실패하면 그 채팅한테 알려줘. Coop could not hand this chat its task (…): <reason> Nothing was claimed, and Dad has been notified. Wait for Dad's instruction; do not start the task on your own. 어댑터는 이걸 A notice from cwkCoop; it asks for nothing but an acknowledgement.로 감싸. 알림은 아무것도 빚지지 않아. 그래서 등록을 붙잡지도 않고, 등록이 은퇴할 때 아직 대기 중인 알림은 취소돼.