
# 워크플로우

워크플로우는 장기 실행 작업을 개별 단계로 나눕니다. 각 단계는 출력을 데이터베이스의 공유 상태 저장소에 저장합니다. PHP가 단계 도중 종료되면 작업자가 이전 상태를 모두 보존한 채 해당 단계를 재시도합니다.

## 워크플로우란?

워크플로우는 순서가 정해진 단계의 연속입니다. 각 단계는 PHP 클래스로, 이전 모든 단계에서 누적된 상태를 받아 새 데이터를 반환해 상태에 병합합니다. 워크플로우는 한 번에 한 단계씩 진행되며, 각 전이는 원자적인 MySQL 트랜잭션으로 처리됩니다.

```php
use Queuety\Queuety;

$workflow_id = Queuety::workflow( 'generate_report' )
    ->then( FetchDataHandler::class )
    ->then( CallLLMHandler::class )
    ->then( FormatOutputHandler::class )
    ->dispatch( [ 'user_id' => 42 ] );
```

## 상태 누적

`dispatch()`에 전달하는 초기 페이로드가 시작 상태가 됩니다. 각 단계에서 반환한 키는 상태에 병합됩니다. `_`로 시작하는 키는 `_next_step` 같은 제어 값으로 예약되며 공개 상태에 병합되지 않습니다:

```
dispatch(['user_id' => 42])

Step 0: FetchDataHandler   -> returns ['user_name' => 'Alice']
  state: {user_id: 42, user_name: 'Alice'}

Step 1: CallLLMHandler     -> returns ['llm_response' => '...']
  state: {user_id: 42, user_name: 'Alice', llm_response: '...'}

Step 2: FormatOutputHandler -> returns ['report_url' => '/reports/42.pdf']
  state: {user_id: 42, user_name: 'Alice', llm_response: '...', report_url: '/reports/42.pdf'}
```

모든 단계는 누적된 전체 상태를 확인할 수 있습니다. 외부 저장소 없이 단계 간에 데이터를 전달하기 쉽습니다.

## 재개 가능성

각 단계가 끝날 때마다 상태가 데이터베이스에 저장되므로 워크플로우는 내구성이 있습니다. 작업자가 1단계 도중 중단되면 0단계의 출력이 상태에 보존된 채 워크플로우가 1단계부터 다시 시작합니다.

단계 경계는 다음 작업을 원자적으로 처리하는 단일 MySQL 트랜잭션입니다:
1. 단계의 출력으로 워크플로우 상태를 업데이트합니다.
2. 현재 단계의 작업을 완료로 표시합니다.
3. 다음 단계의 작업을 대기열에 추가합니다.

이렇게 하면 충돌, 시간 초과 또는 PHP 치명적 오류가 발생해도 상태가 손실되지 않습니다.

## 워크플로우 생명 주기

```
dispatch() -> running -> step 0 -> step 1 -> ... -> step N -> completed
                  |                   |
                  |                   +-> failed -> retry -> running (from failed step)
                  |
                  +-> paused -> resumed -> running
                  |
                  +-> waiting_for_signal -> signal received -> running
                  |
                  +-> waiting_for_workflows -> dependency settles -> running
                  |
                  +-> cancelled (via cancel_workflow)
```

| 상태 | 설명 |
|---|---|
| `running` | 단계를 활발히 처리 중 |
| `completed` | 모든 단계를 성공적으로 완료함 |
| `failed` | 재시도를 모두 소진한 뒤 단계 실패 |
| `paused` | 수동으로 일시 중지됨. 현재 진행 중인 작업은 완료될 수 있지만, 다시 시작할 때까지 후속 단계는 대기열에 추가되지 않음 |
| `waiting_for_signal` | `Queuety::signal()`를 통해 외부 신호를 기다리는 중 |
| `waiting_for_workflows` | `wait_for_workflow()` 또는 `wait_for_workflows()`을 통해 하나 이상의 다른 워크플로우가 완료되기를 기다리는 중 |
| `cancelled` | `Queuety::cancel_workflow()`을 통해 명시적으로 취소됨. 대기 중인 작업은 묻히고 선택적 정리 핸들러가 실행됨. |

## 워크플로우 관리하기

상태 확인:

```php
$state = Queuety::workflow_status( $workflow_id );
echo $state->status->value;   // 'running'
echo $state->current_step;    // 1
echo $state->total_steps;     // 3
print_r( $state->waiting_for ); // ['42'] while waiting on another workflow
print_r( $state->wait_details ); // matched/remaining blockers, result key, correlation info
```

일시 중지 및 다시 시작:

```php
Queuety::pause_workflow( $workflow_id );
Queuety::resume_workflow( $workflow_id );
```

실패한 단계부터 실패한 워크플로우 재시도:

```php
Queuety::retry_workflow( $workflow_id );
```

워크플로우 취소 및 정리 핸들러 실행:

```php
Queuety::cancel_workflow( $workflow_id );
```

정리 핸들러 및 워크플로우 취소 시 동작에 대한 자세한 내용은 [취소](https://queuety.inline0.dev/ko/docs/workflows/cancellation)를 참조하세요.

## 워크플로우 옵션

워크플로우의 모든 단계에 적용할 대기열, 우선순위 및 재시도 동작을 설정하세요:

```php
use Queuety\Enums\Priority;

Queuety::workflow( 'important_pipeline' )
    ->on_queue( 'pipelines' )
    ->with_priority( Priority::High )
    ->max_attempts( 5 )
    ->on_cancel( PipelineCleanupHandler::class )
    ->prune_state_after( 3 )
    ->then( StepA::class )
    ->then( StepB::class )
    ->dispatch( $payload );
```

`on_cancel()` 메서드는 워크플로우가 취소될 때 실행되는 정리 핸들러를 등록합니다. [취소](https://queuety.inline0.dev/ko/docs/workflows/cancellation)를 참조하세요.

`prune_state_after()` 메서드는 워크플로우 상태를 일정하게 유지하기 위해 오래된 단계 출력을 자동으로 제거합니다. [상태 정리](https://queuety.inline0.dev/ko/docs/features/state-pruning)를 참조하세요.

`compensate_with()`를 사용해 단계별 보상 핸들러를 지정하세요. 명시적인 취소뿐만 아니라 워크플로우 실패 후 완료된 단계의 보상을 자동으로 실행하려면 `compensate_on_failure()`를 사용하세요. 자세한 내용은 [보상](https://queuety.inline0.dev/ko/docs/workflows/compensation)을 참조하세요.

장기 실행 오케스트레이션의 안전성을 높이려면 워크플로우 안전 장치를 사용하세요. `version()`는 정의에 애플리케이션 수준 버전 태그를 지정하고, Queuety는 각 실행의 결정론적인 정의 해시도 저장하며, `idempotency_key()`는 재시도나 중복 요청에도 디스패치를 안정적으로 처리합니다. `max_transitions()`, `max_for_each_items()` 및 `max_state_bytes()`는 폭주하기 전에 실행을 실패 처리합니다. 자세한 내용은 [안전 장치](https://queuety.inline0.dev/ko/docs/workflows/guardrails)를 참조하세요.

## 단계 유형

Queuety는 워크플로우 내에서 다음 11가지 유형의 단계를 지원합니다:

- [순차 체인](https://queuety.inline0.dev/ko/docs/workflows/chains): 한 번에 하나씩 실행하도록 `->then()` 사용
- [병렬 그룹](https://queuety.inline0.dev/ko/docs/workflows/parallel): 동시에 실행하도록 `->parallel()` 사용
- [동적 For-Each](https://queuety.inline0.dev/ko/docs/workflows/for-each): 런타임에 발견한 분기 작업과 명시적인 완료 조건에 `->for_each()` 사용
- [비동기 핸드오프](https://queuety.inline0.dev/ko/docs/workflows/async-handoffs): 런타임에 발견한 항목에서 독립적인 하위 워크플로우를 디스패치하도록 `->start_workflows()` 또는 `->start_agents()` 사용
- [조건부 분기](https://queuety.inline0.dev/ko/docs/workflows/conditional): 이름이 지정된 단계 및 `_next_step` 사용
- [반복](https://queuety.inline0.dev/ko/docs/workflows/repeats): 앞서 이름이 지정된 단계로 내구성 있는 역방향 분기를 만들도록 `->repeat_until()` 및 `->repeat_while()` 사용
- [워크플로우 실행](https://queuety.inline0.dev/ko/docs/workflows/run-workflows): 중첩 구성에 `->run_workflow()` 사용
- [내구성 있는 지연](https://queuety.inline0.dev/ko/docs/workflows/delays): 시간 기반 지연에 `->delay()` 사용
- [신호](https://queuety.inline0.dev/ko/docs/workflows/signals): 외부 이벤트 트리거에 `->wait_for_signal()`, `->wait_for_signals()`, `->wait_for_approval()`, `->wait_for_input()` 및 `->wait_for_decision()` 사용
- [워크플로우 종속성](https://queuety.inline0.dev/ko/docs/workflows/dependencies): 워크플로우 간 조정에 `->wait_for_workflow()`, `->wait_for_workflows()` 및 `->wait_for_agents()` 사용
- [스트리밍 단계](https://queuety.inline0.dev/ko/docs/workflows/streaming): 청크 기반 출력을 내구성 있게 처리하도록 `StreamingStep` 인터페이스 사용

## 일반적인 패턴

자율 또는 반자율 시스템을 구축한다면 먼저 [에이전트 오케스트레이션](https://queuety.inline0.dev/ko/docs/workflows/agent-orchestration)을 확인하세요. 실제 플래너/실행기 및 사람 검토 흐름에서 `for_each()`, `start_agents()`, `wait_for_agents()`, `wait_for_workflow()` 및 `wait_for_decision()`가 어떻게 함께 작동하는지 보여줍니다.
