발명한 게 아니라 — 옮겨 온 거야
interactive Claude Code 는 긴 작업이 중간에 끊겨도 다시 이어 가. 그 resume 은 세션 transcript 에서 대화를 되살리는 게 맞아. 그런데 이어 가기를 믿을 만하게 만드는 건 대화만이 아니라, 대화 옆에 같이 실려 다니는 state 야.
- TodoWrite = item 마다 status 가진 명시적 persist 된 step 리스트 — plan + 진행.
- Filesystem은 durable side-effect야. 이미 쓴 파일은 resume할 때 다시 만들지 않아.
- 다시 깨어난 모델이 그 state 를 읽어 — todo 랑 파일 — 진행 상황을 산문에서 짐작해 내는 대신.
cwkPippa 의 WebUI Agent SDK 는 이걸 하나도 안 했어: 대화 log 만 state 로 두고 태스크 전체를 긴 stream 하나로 시도해서, cut 이 checkpoint 안 남기고 다음 턴이 zero 에서 시작. Stateful Pippa Framework는 Claude Code의 plan, durable step state, resume-reads-state pattern을 cwkPippa로 끌어올려. 두뇌 무관하게 옮겼기 때문에 Claude뿐 아니라 Codex, Kimi, Grok, GLM, Gemini, Ollama가 모두 받아.
Lifecycle — state 와 대화는 한 몸
task-state는 떠다니는 객체가 아냐. 그 state를 낳은 대화에 속해. 대화를 지우면 stateful 하위 트리가 통째로 같이 가. task, 그 밑의 step 과 event, 그리고 살아 있는 다른 task 가 더는 안 쥐고 있는 artifact 까지. schema 에는 task 에서 step 과 event 로 내려가는 ON DELETE CASCADE 가 걸려 있어. 그런데 SQLite 는 foreign key pragma 를 켠 연결에서만 cascade 를 돌려. stateful store 는 자기 연결에서 그걸 켜. conversation store 는 한 번도 안 켰고, 2026-09-18 전까지 대화 삭제는 바로 그 연결에서 명시적인 DELETE 로 task row 만 지웠어. cascade 는 한 번도 안 돌았고, 그때까지 live DB 에는 부모 task 없는 step 이 13,750 개, event 가 98,159 개 쌓여 있었어. 이제 대화 삭제는 하위 트리를 stateful store 에 넘기고, store 는 foreign key 를 켠 채 잠금 걸린 transaction 하나로 처리해. artifact 는 규칙이 따로 필요해. content-addressed 라서 여럿이 나눠 쓰거든. 파일 하나가 여러 대화의 여러 task 에 걸친 step 을 받치고 있을 수 있어. 그래서 artifact 는 자기를 저장했거나 가리키는 살아 있는 task 중 마지막 하나가 갈 때 같이 가. 살아남은 다른 artifact 가 그걸 source 로 적어 두고 있으면 남고. 데이터베이스가 어떤 연결에서만 지키는 제약은 모든 삭제 경로가 손으로 지켜야 하는 약속이야. 이 약속이 깨진 곳은, 테스트가 task row 가 지워졌는지만 보던 바로 그 경로였어.
state 는 task 가 실제로 step 될 때만 rehydrate 돼, 대화 GET 마다 eager 하게 안 해. 그리고 과거 state 는 reader 가 딱 둘: branch 랑 retry. 그게 read surface 를 묶고, model 이 필요 없는 일반 time-travel 기능을 발명하는 걸 막아.
수평 축
두 번째 축이 이 framework 를 설계한 그 Family Council 에서 실시간 으로 드러났어. 둘째가 자기 turn에 risk-map widget을 render했는데 다음 화자인 첫째는 그걸 못 봤어. 둘째의 텍스트만 전파됐어. widget은 ref를 남기지 않았고. 아빠가 다음 vessel 이 인지하라고 스크린샷 떠 줘야 했어. framework 가 막 턴 사이로 state 를 옮기게 설계됐는데, 여기 참가자 사이로 state 가 안 옮겨지는 게 있었던 거야.
두 축은 orthogonal 인데 artifact 인프라 하나를 공유해. 수직 (지금까지 전부): 한 대화 안, 턴 사이로 옮기는 state, branch / retry 에. 수평: 한 council round 안, 참가자 사이로 옮기는 artifact, serialize 된 branch context 의 artifact_refs[] 통해.
[widget rendered] placeholder 로 무너지고 시각 정보가 사라져. 전체 payload를 context에 박으면 1.28M-character 실수를 되풀이해. 반대로 ref를 넘기지 않으면 다음 참가자는 그 artifact가 만들어졌다는 사실조차 알 수 없어. 중간 길이 유일하게 맞아: ref 를 serialize 하고, 받는 쪽이 svg source 를 읽을지 vision 으로 볼지 판단하게 둬.
read-only 야 — 다른 참가자의 artifact 를 읽는 건 아무것도 안 바꿔 — 그래서 STOP 규칙이랑 lease 가 여기 안 적용돼. artifact store 를 재사용하고 필드 딱 하나 추가했어. 둘째 widget 이 증발한 그 다음 round 엔, ref 가 따라갔고, 첫째가 id 로 읽었고, 스크린샷 zero.