
# Neuron 세션 상태 머신

만드는 대상이 일회성 실행보다 세션에 가까운 방식으로 동작할 때 유용한 패턴입니다.

Queuety 상태 머신은 세션 수명 주기를 담당하고, Queuety 워크플로는 재시도, for-each 완료 모드 및 집계가 필요한 백그라운드 작업을 수행하며, Neuron는 실제 에이전트 호출과 대화 메모리를 처리합니다.

이 구조는 고객 지원 코파일럿, 사내 조사 보조 도구, 사람의 검토를 위해 일시 중지될 수 있는 편집 에이전트, 그리고 계획·대기·응답 단계 사이를 오가는 채팅 스레드에 적합합니다.

## 수명 주기 구조

이 예제에서는 하나의 지원 세션이 `awaiting_user`, `planning`, `waiting_on_research` 및 `responding`을 거칩니다. 상태 머신은 이 수명 주기를 명시적으로 관리하고, 백그라운드 워크플로는 조사 자료를 모은 뒤 완료되면 세션에 알립니다.

## 상태 머신

```php
use Queuety\Queuety;

$session_id = Queuety::machine( 'support_session' )
    ->version( 'support-session.v1' )
    ->state( 'awaiting_user' )
    ->on( 'user_message', 'planning' )
    ->state( 'planning' )
    ->action( DispatchResearchWorkflowAction::class )
    ->on( 'research_dispatched', 'waiting_on_research' )
    ->state( 'waiting_on_research' )
    ->on( 'research_completed', 'responding' )
    ->state( 'responding' )
    ->action( RespondWithNeuronAction::class )
    ->on( 'response_ready', 'awaiting_user' )
    ->dispatch( [
        'thread_id' => 'support:customer-42',
    ] );
```

사용자가 메시지를 보내면:

```php
Queuety::machine_event(
    $session_id,
    'user_message',
    [
        'session_machine_id' => $session_id,
        'message' => 'Can you compare our pricing to the main competitors?',
    ]
);
```

머신은 `planning`으로 전환하고 작업을 대기열에 넣은 다음 조사 워크플로를 시작합니다.

## 계획 작업: 워크플로 디스패치

```php
namespace App\StateMachines\Actions;

use Queuety\Contracts\StateAction;
use Queuety\Enums\WaitMode;
use Queuety\Queuety;

final class DispatchResearchWorkflowAction implements StateAction
{
    public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string
    {
        $message = trim( (string) ( $event_payload['message'] ?? '' ) );

        $agent_workflow = Queuety::workflow( 'support_research_agent' )
            ->then( ResearchTopicStep::class );

        $research_workflow = Queuety::workflow( 'support_research_packet' )
            ->then( PlanResearchTasksStep::class )
            ->start_agents( 'agent_tasks', $agent_workflow )
            ->wait_for_agents( mode: WaitMode::All, result_key: 'agent_results' )
            ->then( SynthesizeResearchPacketStep::class )
            ->then( NotifySessionResearchReadyStep::class );

        $workflow_id = $research_workflow->dispatch( [
            'machine_id'      => $state['session_machine_id'],
            'thread_id'       => $state['thread_id'],
            'customer_prompt' => $message,
        ] );

        return [
            'latest_user_message'  => $message,
            'research_workflow_id' => $workflow_id,
            '_event'               => 'research_dispatched',
        ];
    }
}
```

이 상태 작업은 직접 조사하지 않습니다. 워크플로를 디스패치하고 워크플로 ID를 머신 상태에 기록합니다.

## 워크플로 단계: 조사가 준비되면 머신에 알리기

```php
namespace App\Workflow\Steps;

use Queuety\Queuety;
use Queuety\Step;

final class NotifySessionResearchReadyStep implements Step
{
    public function handle( array $state ): array
    {
        Queuety::machine_event(
            $state['machine_id'],
            'research_completed',
            [
                'research_packet' => $state['research_packet'],
            ]
        );

        return [];
    }

    public function config(): array
    {
        return [];
    }
}
```

워크플로가 완료되면 `research_completed`을 머신에 보내고, 종합한 자료를 이벤트 페이로드로 포함합니다.

## 응답 작업: 세션 메모리와 함께 Neuron 호출

```php
namespace App\StateMachines\Actions;

use App\Neuron\SupportAgent;
use NeuronAI\Chat\Messages\UserMessage;
use Queuety\Contracts\StateAction;

final class RespondWithNeuronAction implements StateAction
{
    public function handle( array $state, ?string $event = null, array $event_payload = [], array $payload = [] ): array|string
    {
        // Queuety creates actions with `new RespondWithNeuronAction()`, so build dependencies here.
        $pdo = new \PDO( $_ENV['NEURON_DB_DSN'], $_ENV['NEURON_DB_USER'], $_ENV['NEURON_DB_PASSWORD'] );

        $agent = new SupportAgent(
            threadId: $state['thread_id'],
            pdo: $pdo,
        );

        $message = $agent->chat(
            new UserMessage(
                sprintf(
                    "Answer the user message '%s' using this research packet:\n\n%s",
                    $state['latest_user_message'],
                    json_encode( $state['research_packet'] ?? [], JSON_PRETTY_PRINT )
                )
            )
        )->getMessage();

        return [
            'assistant_reply' => $message->getContent(),
            '_event'          => 'response_ready',
        ];
    }
}
```

응답이 생성되면 머신은 공개 상태에 업데이트된 답변을 담아 `awaiting_user`으로 돌아갑니다. 그러면 같은 세션에서 `user_message` 이벤트를 다시 받아 계속 진행할 수 있습니다.

## 큰 워크플로 하나보다 이 방식이 깔끔한 이유

조사에는 계획, 전문 작업 실행, 집계 및 종합이 필요하므로 핵심 작업은 여전히 워크플로 형태가 적합합니다. 장기 실행 지원 대화는 사용자의 다음 입력을 기다리는 중인지, 조사 결과를 기다리는 중인지, 응답을 내보내도 되는지 추적해야 하므로 상태 머신 형태가 적합합니다.

이 분리로 런타임을 이해하기 쉽고 세션 모델도 명확하게 유지할 수 있습니다.

## 관련 문서

- [상태 머신](https://queuety.inline0.dev/ko/docs/state-machines)
- [에이전트 오케스트레이션](https://queuety.inline0.dev/ko/docs/workflows/agent-orchestration)
- [비동기 핸드오프](https://queuety.inline0.dev/ko/docs/workflows/async-handoffs)
- [워크플로 종속성](https://queuety.inline0.dev/ko/docs/workflows/dependencies)
