
# 비동기 핸드오프

워크플로가 발견된 작업을 하나의 실행 안에 두는 대신 독립적인 최상위 워크플로로 넘겨야 할 때 `start_workflows()`을 사용하세요.

에이전트 실행을 명시적으로 모델링한다면 `start_agents()`과 `wait_for_agents()`은 에이전트 중심 기본값을 사용하는 의미상 별칭입니다.

더 큰 규모의 종단 간 예시는 [에이전트 오케스트레이션](https://queuety.inline0.dev/ko/docs/workflows/agent-orchestration)을 참조하세요.

다음과 같은 에이전트 방식의 오케스트레이션에 유용합니다:

- 플래너 워크플로가 런타임에 작업을 발견합니다
- 각 작업은 자체적인 내구성 있는 워크플로가 됩니다
- 부모는 해당 워크플로 ID를 상태에 저장합니다
- 부모는 나중에 `all`, `any`, `quorum` 또는 이름이 지정된 시작 그룹을 기다릴 수 있습니다

## `->start_workflows()` 메서드

```php
use Queuety\Enums\WaitMode;
use Queuety\Queuety;

$agent_task = Queuety::workflow( 'agent_task' )
    ->then( ResearchTopicStep::class )
    ->then( SummarizeTopicStep::class );

Queuety::workflow( 'brief_research' )
    ->then( PlanTopicsStep::class )
    ->start_workflows(
        items_key: 'topics',
        workflow_builder: $agent_task,
        result_key: 'child_workflow_ids',
        group_key: 'researchers',
    )
    ->wait_for_workflow_group( 'researchers', WaitMode::All, result_key: 'child_results' )
    ->then( SynthesizeBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

이 예제에서는 다음과 같습니다.

1. `PlanTopicsStep`이 주제 페이로드 배열을 `$state['topics']`에 기록합니다
2. `start_workflows()`가 주제마다 `agent_task` 워크플로 하나를 실행합니다
3. 시작된 워크플로 ID가 `$state['child_workflow_ids']`에 기록됩니다
4. 시작된 그룹이 선택한 완료 조건을 충족할 때까지 `wait_for_workflow_group()`가 일시 중지됩니다
5. `SynthesizeBriefStep`이 완료된 자식 상태를 `$state['child_results']` 아래에 받습니다

## 자식 페이로드

`items_key`의 각 항목은 시작된 워크플로 하나의 초기 페이로드가 됩니다.

항목이 배열이면 공개 키가 자식 페이로드에 병합됩니다:

```php
[
    [ 'topic' => 'pricing', 'source' => 'competitors' ],
    [ 'topic' => 'reviews', 'source' => 'customers' ],
]
```

항목이 스칼라면 Queuety이 이를 `payload_key` 아래에 저장합니다:

```php
->start_workflows(
    items_key: 'topic_names',
    workflow_builder: $agent_task,
    payload_key: 'topic',
)
```

## 부모 상태 상속

기본적으로 `start_workflows()`은 부모 워크플로의 공개 상태를 모든 자식 페이로드에 병합합니다.

따라서 `brief_id`, `account_id`, `campaign` 같은 공유 컨텍스트를 시작된 각 워크플로에서 자동으로 사용할 수 있습니다.

원본 `items_key`과 `result_key`은 상속에서 제외되므로 각 자식은 전체 작업 목록이나 최종 워크플로 ID 배열을 받지 않습니다.

각 자식이 항목 페이로드만 받아야 한다면 `inherit_state: false`을 설정하세요.

## `run_workflow()`을 사용하지 않는 이유

`run_workflow()`은 부모와 자식 관계를 만들며, 자식이 완료될 때까지 부모는 해당 단계에서 대기합니다.

`start_workflows()`은 더 느슨하게 연결됩니다:

- 시작된 워크플로는 독립적인 최상위 워크플로입니다
- 시작된 각 워크플로를 독립적으로 살펴보거나 재시도하거나 내보낼 수 있습니다
- 부모는 나중에 해당 워크플로를 기다릴지, 어떻게 기다릴지 결정합니다

따라서 `start_workflows()`은 비동기 에이전트 작업 핸드오프에 더 적합하고, `run_workflow()`은 하나의 오케스트레이션 트리 안에서 중첩 구성을 할 때 더 적합합니다.

## 나중에 기다리기

`start_workflows()`은 [워크플로 종속성](https://queuety.inline0.dev/ko/docs/workflows/dependencies)과 함께 사용하도록 설계되었습니다:

```php
use Queuety\Enums\WaitMode;

Queuety::workflow( 'review_board' )
    ->start_workflows( 'review_tasks', $review_task_workflow, 'review_workflow_ids' )
    ->wait_for_workflows( 'review_workflow_ids', WaitMode::Any, 'winner' )
    ->then( FinalizeDecisionStep::class )
    ->dispatch( $payload );
```

- `WaitMode::All`은 완료된 모든 자식 상태를 수집합니다
- `WaitMode::Any`은 첫 번째 성공한 자식 워크플로가 완료되면 재개됩니다

전체 런타임 편집 가능 DAG 엔진 없이도 플래너/실행기 오케스트레이션을 구현할 수 있습니다.

## 에이전트 친화적 별칭

`start_agents()`은 플래너/실행기 흐름에 맞춘 기본값을 사용하는 `start_workflows()`의 별칭입니다:

- `result_key`의 기본값은 `agent_workflow_ids`입니다
- `payload_key`의 기본값은 `agent_task`입니다
- `name`의 기본값은 `start_agents`입니다

`wait_for_agents()`은 에이전트 결과 수집에 맞춘 기본값을 사용하는 `wait_for_workflows()`의 별칭입니다:

- `workflows`의 기본값은 `agent_workflow_ids`입니다
- `result_key`의 기본값은 `agent_results`입니다
- `name`의 기본값은 `wait_for_agents`입니다

`wait_for_agent_group()`은 에이전트 배치에 맞춘 기본값을 사용하는 `wait_for_workflow_group()`의 별칭입니다.

```php
use Queuety\Enums\WaitMode;

$agent_run = Queuety::workflow( 'agent_run' )
    ->then( ResearchTopicStep::class )
    ->then( SummarizeTopicStep::class );

Queuety::workflow( 'brief_research' )
    ->then( PlanTopicsStep::class )
    ->start_agents( 'agent_tasks', $agent_run, group_key: 'researchers' )
    ->wait_for_agent_group( 'researchers', WaitMode::Quorum, 2 )
    ->then( SynthesizeBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```
