
# 워크플로우 안전 장치

에이전트형 워크플로우는 유연하지만, 중복 실행을 만들거나 플래너가 의도한 것보다 많은 작업을 생성하기도 쉽습니다. Queuety는 워크플로우 수준에서 가벼운 안전 장치를 제공하므로 유연성을 유지하면서도 제어력을 잃지 않을 수 있습니다.

작업자 승인 및 작업별 비용 메타데이터를 다루는 더 폭넓은 리소스 모델은 [리소스 관리](https://queuety.inline0.dev/ko/docs/features/resource-management)를 참조하세요.

## 워크플로우 정의에 버전 지정하기

`version()`를 사용해 워크플로우 정의에 애플리케이션 수준의 버전 레이블을 지정하세요:

```php
Queuety::workflow( 'research_run' )
    ->version( 'research.v2' )
    ->then( PlanResearchStep::class )
    ->then( DraftResearchSummaryStep::class )
    ->dispatch();
```

버전은 워크플로우 상태와 함께 저장되고, `workflow_status()`를 통해 확인할 수 있으며, `wp queuety workflow status`에 출력되고 워크플로우 내보내기에 포함됩니다.

Queuety는 각 워크플로우 실행에 대해 결정론적인 `definition_hash`도 저장합니다. 해시는 직렬화된 워크플로우 정의에서 생성되며, 배포 후 진행 중인 실행이 정확히 어떤 워크플로우 구조를 실행 중인지 식별할 때 유용합니다.

## 디스패치에 멱등성 적용하기

호출자가 같은 요청을 재시도할 수 있는 경우 `idempotency_key()`를 사용하세요:

```php
Queuety::workflow( 'research_run' )
    ->idempotency_key( 'brief:42:research' )
    ->then( PlanResearchStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

이미 사용된 키로 워크플로우를 다시 디스패치하면 Queuety는 중복 실행을 만들지 않고 원래 워크플로우 ID를 반환합니다. 키는 워크플로우 이름 전반에서 전역적으로 사용되므로 서로 다른 워크플로우가 같은 키를 사용할 수 있다면 키에 워크플로우 이름을 포함하세요.

다음과 같은 경우 특히 유용합니다:

- 클라이언트나 게이트웨이가 재시도할 수 있는 API 엔드포인트
- 중복 전달을 받을 수 있는 웹훅 소비자
- 외부 엔터티마다 오케스트레이션 실행을 하나만 생성해야 하는 플래너 단계

멱등성 키는 `queuety_workflow_dispatch_keys` 테이블에 저장됩니다.

## 워크플로우 예산 설정하기

계획한 한도를 초과하는 실행은 워크플로우 예산으로 실패 처리하세요:

```php
Queuety::workflow( 'research_run' )
    ->max_transitions( 20 )
    ->max_for_each_items( 12 )
    ->max_state_bytes( 32768 )
    ->max_cost_units( 40 )
    ->max_started_workflows( 8 )
    ->then( PlanResearchStep::class )
    ->for_each( 'tasks', ExecuteResearchTask::class, 'results' )
    ->dispatch();
```

### `max_transitions()`

실행이 실패하기 전에 완료할 수 있는 워크플로우 단계 수를 제한합니다. 동적으로 분기하거나 `_next_step`를 통해 같은 영역을 다시 방문할 수 있는 워크플로우를 보호하는 데 유용합니다.

### `max_for_each_items()`

단일 `for_each()` 단계가 확장할 수 있는 런타임 발견 항목 수를 제한합니다. 에이전트가 의도한 것보다 훨씬 많은 분기 작업을 찾을 수 있는 플래너/실행기 시스템에 유용합니다.

### `max_state_bytes()`

공개 워크플로우 상태의 인코딩된 크기를 제한합니다. 이를 통해 장기 실행 워크플로우가 검사, 내보내기 또는 재시도에 시간이 오래 걸리는 대형 데이터 덩어리로 커지는 것을 방지할 수 있습니다.

### `max_cost_units()`

완료된 단계가 소비하는 총 비용 단위를 제한합니다. 비용 단위는 작업을 대기열에 추가할 때 Queuety가 확인하는 단계 또는 작업 메타데이터에서 가져옵니다. 일반 사용자 작업의 기본값은 `1`이고, 지연 및 대기 같은 오케스트레이션 플레이스홀더는 `0`를 사용합니다. 핸들러는 `config()` 또는 작업 속성을 통해 더 높은 양수 값을 설정할 수 있습니다. 단계 정의의 `cost_units`만 `0` 값을 설정할 수 있습니다.

워크플로우가 같은 영역을 다시 방문할 수 있거나 For-Each 작업의 비용이 단계 수만으로 파악되지 않는 경우 유용합니다. 비용이 큰 모델 호출을 계속 반복하는 플래너는 `max_transitions()` 이내에 머물면서도 의도한 비용 한도를 초과할 수 있습니다.

### `max_started_workflows()`

`start_workflows()` 또는 `start_agents()`를 통해 실행이 시작할 수 있는 최상위 워크플로우 수를 제한합니다. 하위 워크플로우를 디스패치하기 전에 한도를 확인하므로, 예산을 이미 초과하는 그룹을 일부만 생성한 상태로 부모 워크플로우가 실패하는 일을 방지합니다.

예산을 초과하면 Queuety는 단계를 재시도하는 대신 워크플로우를 즉시 실패 처리합니다. 예외가 한 가지 있습니다. `Queuety::signal()`가 대기를 완료하고 그 결과로 예산을 초과하는 전이가 발생하면 예외가 `signal()` 호출자에게 전달되고, 신호 삽입은 롤백되며 워크플로우는 계속 대기합니다.

## 안전 장치 관찰하기

워크플로우 상태에서 다음 정보를 확인할 수 있습니다:

- `definition_version`
- `definition_hash`
- `idempotency_key`
- `budget`

`budget` 페이로드에는 설정된 한도와 현재 사용량이 포함되며, 현재 공개 상태의 크기(바이트)도 확인할 수 있습니다.

예시:

```php
$state = Queuety::workflow_status( $workflow_id );

$state->definition_version; // 'research.v2'
$state->definition_hash;    // '4c7a...'
$state->idempotency_key;    // 'brief:42:research'
$state->budget;             // [ 'max_transitions' => 20, ... ]
```
