PoCs

사는 것은 스케줄링이 아니라 재개(resume)다

cron·큐·상태 컬럼은 전부 **어디까지 했는지를 개발자가 저장하게** 만듭니다. 지속 실행(durable execution) 엔진은 **모든 단계를 저널에 남기고**, 워커가 죽으면 **그 저널을 재생해 메모리 상태를 정확히 그 지점으로 복원**합니다 — 그래서 `await sleep(3일)` 이 **재시작을 몇 번 겪어도 진짜로 3일을 기다립니다.** 대가는 **결정성**입니다 — 워크플로 코드 안에서 **시계·난수·네트워크 호출 금지**, 그리고 **그 제약이 학습 곡선의 거의 전부**입니다.

아직 만들지 않았습니다

이 페이지는 카드에 적힌 내용을 펼쳐 보여줄 뿐입니다 — 돌아가는 코드도, 열어볼 데모도 아직 없습니다. 무엇을 왜 만들려는지가 아래에 있습니다.

어떻게 보나

**반 시간이고, 목적은 읽는 게 아니라 기계장치를 눈으로 보는 것**입니다. 로컬 개발 서버를 띄우고, **sleep 이 들어간 루프 워크플로 하나**를 만들되 하루가 아니라 **10초**로 두고 돌립니다. 그다음 **도중에 워커를 죽였다가 다시 켜서** — **처음부터가 아니라 이어서 도는 것**을 봅니다. 그리고 UI 에서 **그 실행의 이벤트 히스토리**를 엽니다. **거기서 이 도구가 마법이기를 그만둡니다** — **재개는 기능이 아니라 재생되는 로그**입니다. 함정 둘은 **프로덕션이 아니라 여기서 일부러** 밟아 보십시오. 워크플로 안에 **`Date.now()`** 를 넣고 **재생 때 경로가 조용히 갈라지는 것**을 봅니다 — **부수효과가 액티비티로 가야 하는 이유**입니다. 그리고 **실행 중인 워크플로의 코드를 바꿔** 봅니다 — **버전 게이팅이 존재하는 이유**입니다. 여기의 세부(요금제, 투자 라운드, 에이전트 프레임워크 통합)는 **2026-08-28 기준 독해**이므로 인용 전에 확인하십시오.

기술 노트

각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.

사는 것은 스케줄링이 아니라 재개(resume)다준비 중

목적: **범주 이름이 제품을 가립니다.** *"워크플로 엔진"* 은 스케줄링처럼 들리는데, **스케줄링은 이미 갖고 있는 부분**입니다 — cron 이 쏘고, 큐가 배달하고, 상태 컬럼이 기억합니다. **그중 어느 것도 주지 않는 것**은, 여러 단계짜리 과정의 **한복판에 있다가 기계가 죽어도 살아남는 능력**입니다. 결국 모든 팀이 그걸 직접 만들고, 만들어지는 것은 **상태 enum·재시도 컬럼·가드 절에 흩어진 수제 상태 머신**입니다. **그 코드가 버그의 서식지**입니다 — **재개 가능한 과정을, 그것을 표현할 방법이 없는 언어로** 적어 놓았기 때문입니다. **결정성은 특이한 규칙이 아니라 거래 조건 그 자체입니다.** 재생은 **같은 히스토리가 항상 같은 경로를 태울 때만** 성립하고, 그래서 워크플로 코드 안의 **시계 읽기나 난수가 그것을 깹니다** — 그것도 **조용히**, **복구 시점에**, 바꾼 지 한참 뒤에. **부수효과를 액티비티로 미는 것은 격식이 아니라** *"재생될 수 있는 부분"* 과 *"재생되면 안 되는 부분"* **사이의 선**입니다. **그 구분 하나를 이해한 팀은 이 도구의 대부분을 이해한 것**이고, 못 한 팀은 한 달을 싸웁니다. **그리고 2026년에 이 범주가 시끄러워진 이유는 마이크로서비스가 아니라 에이전트입니다.** 에이전트 실행은 **길고, 사람을 기다리고, 도구를 순서대로 부르고, 도구 호출 히스토리를 지닌 채 크래시에서 살아남아야** 합니다 — **지속 실행이 이미 풀어 둔 문제의 정확한 재진술**입니다. 이건 이 벤더 하나를 넘어 알아 둘 값이 있습니다 — **새 작업 부하가 옛 원시 기능을 필요로 한다고 밝혀지면, 그 원시 기능은 재발명되는 게 아니라 값이 다시 매겨집니다.** 정산 흐름도 들어맞음이 똑같이 직접적입니다 — **마감 → 오라클 확정 대기 → 분쟁 창 → 정산 → 출금** 은 **매 단계에 외부 대기가 있는 장기 실행**입니다 — 다만 **선택이 아닌 단서 하나**: **온체인 트랜잭션은 재시도 아래에서 멱등해야 합니다.** **nonce 나 요청 id 를 재시도되는 액티비티 *바깥*에서 고정**하십시오. 안 그러면 **이 엔진의 가장 쓸모 있는 기능이 조용히 이중 전송이 됩니다.** 정직한 비교는 **같은 급에서 가장 강력하고 동시에 가장 무겁다**는 것입니다 — 필요한 게 **야간 작업 하나뿐이라면 더 가벼운 이벤트 러너가 더 빨리 답**하고, 무거운 쪽을 고르는 이유는 **나중에 같은 엔진이 정산 흐름까지 태울 것**이기 때문입니다.

동작 방식: ### 각 도구가 실제로 저장하는 것 | 도구 | 진행 상황을 기억하는 주체 | 도중 크래시 생존 | | --- | --- | --- | | cron | **개발자** — 상태 컬럼에 | 아니오 — 처음부터 | | 큐 | 개발자 — 메시지 단위로 | 부분적 — 메시지 입도까지 | | 수제 상태 머신 | 개발자 — enum·가드 절에 | 코딩한 곳까지만 | | **지속 실행** | **엔진** — 이벤트 히스토리로 | **예 — 정확히 그 단계로 재생** | ### 거래 조건, 한 줄씩 - **얻는 것**: 진짜로 기다리는 `await sleep('3 days')`, **코드가 아니라 선언으로 끝나는 재시도**, 단계별로 들여다볼 수 있는 실행. - **주는 것**: **결정성** — 워크플로 코드 안에서 `Date.now()`·`Math.random()`·직접 네트워크 호출 금지. - **경계선**: 비결정적인 것은 전부 **액티비티**로 — **재생 가능/불가능의 경계**가 그것입니다. ### 함정 둘, 둘 다 로컬에서 먼저 밟을 것 1. **워크플로 안의 시계.** 재생이 원래 실행과 **다른 분기**를 타고 **조용히**, **복구 시점에** 깨집니다 — **가능한 최악의 시점**입니다. 2. **실행 중에 워크플로 코드 배포.** 히스토리와 코드 경로가 어긋납니다 — **버전 게이팅이 정확히 이걸 위해 있고, 첫날부터** 써야 합니다. ### 온체인 정산에는 규칙이 하나 더 **재시도가 곧 기능**이므로, 트랜잭션이 그 아래에서 **멱등**해야 합니다. **nonce·요청 id 를 재시도되는 액티비티 *바깥*에서 고정**하십시오. 안 그러면 **일시적 RPC 오류가 이중 전송**이 됩니다. ### 무게를 정직하게 고르기 | 필요 | 합리적인 답 | | --- | --- | | 재개돼야 하는 야간 작업 하나 | **가벼운 이벤트 러너가 더 빨리** 답합니다 | | 정산: 마감 → 오라클 → 분쟁 → 정산 → 출금 | **무거운 엔진** — 흐름이 진짜로 장기 실행 | | "둘 다 필요할지도" | **두 번째 사례에서도 여전히 원할 쪽**을 고르십시오 |