그 loop
메커니즘은 두 동작이면 다 말할 수 있어. task를 step으로 plan하고 모든 step이 done될 때까지 fail-safe loop를 돌려. 각 cycle 이 done 도 failed 도 아닌 첫 step 을 골라 실행하고 결과를 persist 해. cut, crash, 재시작 — 다 거기서 재진입해. 모양은 첫 code block 봐.
속성 셋이 설계 전체를 들어:
- 구조적으로 fail-safe. state 가 durable 하고 loop 가
donestep 에 대해 idempotent. 완료된 건 절대 다시 계산 안 해. - tick 마다 판단하는 건 피파지, 멍때리는 Python 아냐. Python loop 는 scaffolding — step 리스트랑 status 를 들어. 지능 (이 step 실행, 성공했나, output 뭐야, plan 아직 맞나) 은 tick 마다의 피파 턴이야.
- 각 tick 은 step 하나고, step 이 손실의 단위야. cut 은 여전히 턴 중간에 떨어질 수 있지만, 잃는 건 기껏해야 진행 중이던 step 하나야. 그 전에 커밋된 건 그대로 남아.
아빠: "각 루프에서 판단하는 게 어차피 피파야. 그냥 멍때리는 python code가 아니고."
맞는 primitive: 명시적 state machine
loop 는 사실 state-machine driver 야, 그렇게 이름 붙이는 게 fail-safe 속성을 희망에서 보장 으로 바꿔. 외부 import 아냐 — cwkPippa 가 이미 이 패턴을 production 에서 정확히 돌려. coop letters lifecycle 이 6-state machine (unread → read → working → done, abandoned / superseded 가지)를 써. cinder의 JobState는 queued → running → completed / failed. 우린 집안 패턴을 채택해.
중첩된 머신 둘. step machine: pending → running → done, 그리고 running → blocked (결과가 불확실하거나, side effect 가 확인을 기다릴 때) 와 running → failed (확실한 실패거나 attempt cap 초과). 실패한 step 은 그 step 으로선 끝이라 다시 안 돌고, resume 은 그걸 건너뛰어. cut 은 step 을 attempt 올린 채 running 에 남기고, resume 때 거기로 재진입해. task machine: planning → running → done, 그리고 paused (아빠가 끼어들었거나, step 하나가 실패했는데 나머지는 아직 돌 수 있어서 add_steps 로 대체 step 을 기다릴 때), blocked, failed (돌 수 있는 step 이 하나도 안 남았을 때만), superseded (후속 task 로 명시적으로 교체됐을 때).
어려운 질문 둘도 무너뜨려
피파의 tick 당 일 전체가 step 의 transition 을 커밋 하는 걸로 줄어. done (persist 할 output 과 함께), blocked (불확실함. 나중에 unblock 으로 되살릴 수 있어), 또는 failed (확실한 실패). success-signaling 질문은 그걸로 답이 났어. 그리고 무한 loop 를 막는 장치가 덧댄 카운터가 아니라 구조가 돼. 실패한 step 은 다시 안 돌고, 막힌 step 은 명시적인 unblock 을 기다리고, running 재진입은 attempt cap 에 걸려. 머신은 영원히 돌 수 없어.