일보다 턴이 먼저 끝나
Pippa가 coop 도구로 todo를 맡기거나, 배포를 시작하거나, 재시작을 요청해. 일은 10분 뒤에, 혹은 한 시간 뒤에 끝나. 턴은 지금 끝나고. 그러면 나쁜 선택지 둘이 남아. 하나는 계속 붙어서 들여다보는 거야. 그러면 대화가 인질로 잡히고, 상태 필드 하나 지켜보느라 토큰을 태워. 다른 하나는 "나중에 확인할게"라고 말하는 건데, 그건 지켜 줄 누구도 없는 약속이야. 턴이 끝나면 모델한텐 시계도 없고, 약속한 기억도 없고, 스스로 깨어날 방법도 없거든.
cwkPippa의 답은 followup 가족 도구야. 위임한 턴이 돌아가기 전에, 오래 남고 읽기만 하는 확인을 하나 걸어 둬. 뭘 볼지, 뭘 끝난 걸로 칠지, 뭘 막힌 걸로 칠지, 얼마나 자주, 언제까지, 그리고 경우마다 아빠한테 할 한 줄까지. 거는 시점에 대해선 도구 설명부터 단호해. 턴을 끝내기 전에 걸고, 돌려받은 wake plan을 말하라고.
깨우기 계약
걸기 호출은 딱 이 필드만 받아.
key: 이 일의 안정적인 이름.inspect: 어떤 증거를 읽을지 말로 적은 것.probe: 기계가 읽는 확인.coop아니면http.success_message와blocked_message: 각각 한 줄, 아빠의 언어로. 줄바꿈이 들어가면 거절이야("Notifications must be one line").interval_seconds(기본 30)와timeout_seconds(기본 3,600): 5 이상의 정수고, 기한은 간격보다 짧으면 안 돼.
출처는 아빠 본인의 Pippa 대화에 저장된 턴이어야 해. coop 도구가 지키는 것과 같은 규칙이야. 돌려받는 건 wake_plan이 풀어 적힌 저장 기록이고. pending이면 조용히 다시 봐. busy, idle, 대기열 영수증은 완료가 아니니까. success면 성공 줄을 Master Chief에 한 번 올리고 바깥 알림은 없어. blocked면 아빠한테 바로 알리고 기한까지 계속 봐. 기한이 되면 확인을 멈추고 완료를 확인하지 못했다고 보고해. 일을 다시 하는 일은 절대 없고.
탐침 둘, 둘 다 읽기 전용
coop 탐침은 work_kind와 item_id로 작업을 지목하고, 지난 레슨의 그 제어 파사드를 통해 읽어. 쓰는 건 읽기 동작인 show뿐이야. 판정은 일부러 좁아.
- 성공은
done이나closed인 항목뿐이야. - 최신 보고가
blocked,abandoned,released면 막힘이야. 아직 아무도 작업을 가져가지 않았는데 배정이 실패하거나 취소되거나 만료돼도 막힘이고. 그렇다고 옛날에 만료된 초대 하나가 수락한 워커의 보고를 덮어쓸 순 없어. - 나머진 전부 대기야. busy, idle, 대기열 영수증, 심지어 저장된 disconnected 상태도. 그건 지금 턴보다 오래된 관찰일 수 있거든.
message_id를 붙이면 탐침은 그 대신 답글 하나를 지켜봐. 5트랙에서 본 applied나 not_applied 답이 그 답글에 오길 기다리는 거야. 답글 전달이 실패했거나 불확실하거나, 워커가 연결돼 있지 않으면 막힘이야.
http 탐침은 자격 정보 없는 JSON 상태 URL을 GET해. URL에 사용자나 비밀번호 없고, 인증 헤더 없고, 리다이렉트 안 따라가고, 환경의 프록시 설정도 안 써. success 조건은 필수야. 점으로 이은 키를 정확한 스칼라 값에 대응시키는데, 타입까지 맞아야 해. blocked 조건은 있어도 되고 없어도 되고. 401이나 403은 막힘이야. 기다린다고 권한 문제가 풀리진 않으니까. 다른 실패는 잠깐 지나가는 대기로 쳐. 저장하는 증거는 조건이 지목한 필드뿐이고, 응답 전체는 절대 안 남겨. 도구는 호출하는 쪽한테 기대하는 빌드나 버전을 못 박으라고 해. 옛날 프로세스가 멀쩡하게 200을 돌려주는 건 끝난 배포가 아니거든.
같은 키, 같은 감시
후속 확인의 id는 대화 id와 키를 묶은 SHA-256이야. 같은 키, 같은 계약으로 다시 걸면 있던 감시를 그대로 돌려받아. 같은 키에 다른 계약을 걸면 거절이야. 이 키는 이미 다른 깨우기 계약을 가리키고 있으니 먼저 살펴보고 새 키를 고르라고. 이게 막는 경우는 평범하고 흔해. 도구 결과가 사라지고, 아빠가 다음 턴에 다시 묻고, Pippa가 또 걸어. 일 하나에 감시 하나.
결과마다 하는 일
- 대기는 조용해. 확인할 때마다 관찰 기록엔 남지만 아무한테도 안 알려.
- 성공이면 알림은 필요 없음으로 표시하고, 레슨 2의 그 영속
master_chief.report로success_message를 Master Chief에 한 번 올려. 키로는 그 후속 확인을 써. 텔레그램은 없어. 아빠는 다음에 Pippa랑 얘기할 때 읽게 돼. - 기한 전에 막힘이 오면 첫 막힘을 영수증과 함께 한 번만 저장해. 그리고 아빠한테 텔레그램으로 알린 다음 Master Chief에 올려. 감시는 계속 대기 상태로 남아. 나중에 성공하면 그건 그대로 따로 보고돼.
- 성공 없이 기한이 오면
deadline_without_completion_evidence라는 이유를 단 막힘이 돼. 마지막 탐침 자체는 막힘을 못 봤다면(잠깐의 장애 같은 거), 가장 최근의 진짜 막힘을 시각과 함께 지우지 않고 남겨. 성공으로 치는 일은 절대 없고, 원래 일을 다시 시도하지도 않아. 이때 아빠가 읽는 줄도 진짜 막힘 때와 같은blocked_message야._blocked_text엔 기한 전용 문구가 따로 없거든. 마지막 탐침에for_dad가 없을 때 직전 진짜 막힘의for_dad를 빌려 올 뿐이야. 그러니blocked_message는 두 경우 모두에 맞게 써("실패했거나 기한 안에 확인이 안 됐어"). 일이 실패했다고 단정하는 문장으로 쓰면 안 돼.
막힘에서 아빠가 읽는 건 걸 때 적어 둔 blocked_message와 워커 본인의 for_dad 줄, 그게 다야. 이것도 고친 결과야. 2026-10-10까지는 알림 뒤에 워커의 보고 본문 전체, 그러니까 영어 이력 기록이랑 이유 코드까지 붙었어. 그래서 막힘 하나(todo #373)가 텔레그램과 Master Chief에 영어 벽으로 도착했지. 커밋 7a404b4e(todo #375)가 그걸 머리 줄과 for_dad로 줄였고, 영어 이유는 status 동작이 볼 수 있게 관찰 기록에 남겨 뒀어. 같은 커밋이 성공 때 붙던 영어 "recovered" 꼬리도 뗐어. 아빠가 읽는 건 success_message야. (cwkPippa의 follow-up 문서엔 아직 예전처럼 이유를 덧붙인다고 적혀 있어. 맞는 건 코드 쪽이야.)
먼저 예약하고, 그다음 보내
레슨 2의 notify 문은 텔레그램을 보낸 뒤에 보냈다고 표시해. 드문 중복은 감수하는 거지. 후속 확인의 배달은 반대야. 텔레그램을 부르기 전에 alert_state='sending'을 저장하고, 끝나면 sent로 바꿔(채널이 거절하면 다시 pending으로 돌려서 재시도하고). 두 저장 사이에 프로세스가 죽으면, 다음 실행은 sending을 보고 다시 보내는 대신 uncertain을 기록해. status 동작은 그 불확실함을 작업 결과와 따로 보여주고. 이렇게 해도 되는 이유는 바로 다음 단계에 있어. Master Chief 보고는 그래도 배달되니까, 알림이 불확실해도 그 줄은 아빠 대화에 남아. 급한 텔레그램을 먼저 보내는 것도 바로 그래서야. 보고 호출에 2초 제한이 걸려 있긴 해도, 바쁜 Chief가 그 알림까지 늦추게 둘 순 없으니까.
새 cron은 없어
이 중 어느 것도 스케줄러를 새로 만들지 않아. 시작할 때 cwkPippa는 HeartbeatService가 이미 쓰는 APScheduler에 5초짜리 interval 작업 followup-checks를 하나 등록해. 그 스케줄러는 프로세스 간 스케줄러 임대를 쥔 프로세스에서만 돌고. 틱마다 때가 된 확인을 나열하고, 행마다 2분짜리 임대를 잡고, 탐침은 별도 태스크로 띄워. 그래서 틱 자체는 가벼워. 기록은 대화 데이터베이스 옆에 있는 자기 SQLite 파일에 들어 있어서, 재시작해도 살아남아. cron 스냅숏 복원이 스케줄러를 비우면 리스너가 그 작업을 다시 넣어. launchd 작업도 없고, 확인마다 모델을 부르지도 않아. 그리고 cancel은 확인만 거둬들이지, 지켜보던 일은 절대 안 건드려.