자꾸 되풀이되던 실패 두 개
Coop에 엔진이 생기기 전엔 장부가 습관으로 굴러갔어. 워커 세션이 일을 시작하기 전에 todo take, 끝나면 todo done을 치기로 돼 있었지. 그런데 가끔은 배정받은 일을 take 없이 그냥 시작했어. 더 흔한 건 반대쪽이야. 긴 작업을 끝낼 즈음엔 닫으라는 당부가 이미 저 위로 밀려 올라가 있었거든. 그걸 알아채는 건 늘 아빠였고, 장부는 아빠가 손으로 고쳤어. Almanac에서 직접 하든지, 다른 피파한테 고쳐 달라고 하든지.
당부를 더 잘 쓰면 되지 않나 싶지? Coop 아키텍처는 그 길을 한 문장으로 막아. 당부를 다른 피파 프롬프트로 옮겨봤자 같은 실패가 남는다고. 당부도 결국 모델이 기억해야 하는 거고, 무너진 게 바로 그 기억이었으니까.
아무도 기억할 필요 없는 사실 두 개
그래서 엔진은 사실 두 개를 쥐고 있어. 어떤 모델의 기억과도 상관없이.
- 실제로 어느 세션이 정확히 어느 작업 항목을 받았나. 행위자 이름도, 폴더도 아니야. 세션이야.
- 받아 간 항목 중에 아직 결과나 다른 결정 기록이 필요한 게 뭔가. 누가 뭔가를 기록할 때까지 계속 빚으로 남는 의무.
작업이 수락 기준을 채웠는지는 여전히 모델이 판단해. 엔진이 보장하는 건 그 판단이 기록될 때까지 빚으로 남는다는 것뿐이야. 이 트랙은 첫 번째 사실을 다뤄. "세션이 있다"와 "세션이 이 일을 가졌다" 사이의 틈. 잘못된 소유 주장은 늘 거기로 새어 들어왔어.
행이 먼저야
아빠가 todo를 배정하면, 워크스페이스에서든 Telegram에서든 피파를 통해서든, 제일 먼저 생기는 건 데이터베이스 행이야. Assignments.create가 멱등 엔진 커맨드 안에서 engine_assignments 행을 prepared 상태로 써. 행에는 항목과 현재 리비전, 벤더, 프로젝트, 모드, 새로 뽑은 무작위 마커, 저장된 셋업 프롬프트, 작업 범위, 추론 강도가 들어가. 이 시점엔 어느 맥에서도 아직 아무것도 안 움직였어.
네이티브 쪽 효과는 전부 그다음이야. 코디네이터는 앱을 열거나 프로세스를 띄우기 전에 행을 awaiting_session으로 옮기고, 이유는 주석에 적어 뒀어. Persist BEFORE the external effect. On restart, discover; never open a second native chat automatically. 백그라운드 실행이 시간 초과로 끝나도 Coop은 다시 띄우지 않아. 그 실행이 띄웠을지도 모르는 세션 하나를 계속 찾아. 요청 하나에 채팅 하나. 재시작을 거쳐도 마찬가지야. Claude new 채팅만은 기다려 주지 않아. office에서 앱을 못 열면, 시간 초과까지 포함해서, 배정은 바로 실패해. 그리고 아빠한테 거기서 앱을 열고 다시 해보라는 말이 가.
세션을 얻는 네 가지 길
| 모드 | 벌어지는 일 |
|---|---|
new | 새 채팅. Claude Code면 office에서 앱 작성창이 셋업 프롬프트가 채워진 채로 열리고, 사람이 Send를 눌러. Codex면 비공개 상주 App Server가 채팅을 만들어 셋업 턴만 돌리고 내려간 다음, Codex 앱이 바로 그 스레드를 열어. Send 누를 사람이 필요 없어. |
manual | "셋업 브리프 복사." 아무것도 안 열려. Coop이 저장된 브리프를 보여주고, 아빠가 새 채팅의 첫 메시지로 직접 보내. office를 비워서 거기서 Send 누를 사람이 없을 때 쓰는 길이야. |
existing | 이미 열려 있는 채팅. 제목, 폴더, 상태를 보고 골라. 기다릴 게 없으니까 awaiting_session은 건너뛰어. |
remote | Telegram이랑 피파의 run이 기본으로 쓰는 길. Claude Code가 claude --bg --remote-control로 백그라운드에서 뜨고, 세션에 Remote Control 링크가 보이면 Coop이 그걸 집어 와. Codex는 new랑 같은 비공개 App Server 부트스트랩을 타. |
모드가 뭐든 행이 밟는 상태는 같아. prepared → awaiting_session → enrolling → dispatched → resolved 아니면 declined. 여기에 failed, expired, cancelled가 명시적인 종착 상태로 붙어. 활성 상태 넷은 코드에선 튜플 하나로 묶여 있어. 저장소는 항목 하나가 이 넷 중 어느 상태로든 배정을 하나까지만 갖게 강제해. 수단은 둘이야. 유일 부분 인덱스 하나, 그리고 살아 있는 디스패치가 이미 그 항목을 쥐고 있지 않은지 보는 검사 하나. 버튼을 두 번 눌러? 두 번째 클릭은 충돌로 끝나.
처음 켜지는 추론 강도
작은 필드 하나에도 사연이 있어. 2026-10-10 전까지 Coop은 워커의 추론 강도를 기록만 하고 실행할 땐 안 넘겼어. 그래서 Coop이 띄운 Claude Code 워커는 전부 CLI 기본값인 medium으로 돌았지. 아빠 기준은 high인데 (커밋 adcc5ab). 지금은 launch_effort가 두 벤더 공통의 유일한 해석기야. 요청에 단계가 따로 없으면 high고, 가족의 max는 Codex에선 xhigh로 내려가. Codex 눈금은 거기가 끝이거든. 강도가 닿는 건 Coop이 직접 띄우는 프로세스뿐이야. Claude 백그라운드 세션(remote)은 --effort를 받고, Codex 부트스트랩(new, remote)은 그 단계로 스레드를 만들어. Claude new 채팅은 못 받아. 작성창 링크엔 폴더랑 프롬프트만 실리거든. 그래서 그 채팅은 앱에 설정된 강도로 돌아. 이미 열려 있는 채팅이나 아빠가 manual 브리프로 연 채팅도 마찬가지고.
open이야. 채팅이 직접 todo take를 치는 다른 문은 8트랙 몫이고. 그 수락을 어떻게 짓는지가 이 트랙의 나머지야.