Neuron 메모리 + 검토 상태 머신
에이전트가 사람의 검토 라운드가 반복되는 동안 대화 맥락을 유지해야 하지만, 전체 수명 주기는 여전히 Queuety이 조율해야 할 때 유용한 패턴입니다.
Queuety은 검토 상태 머신을 담당하고, Neuron은 초안 작성 대화의 에이전트 메모리를 담당합니다.
Neuron 메모리가 유용한 이유
검토자가 초안을 거부하고 수정을 요청해도, 다음 에이전트 호출에서 전체 맥락을 처음부터 다시 구성할 필요가 없습니다.
에이전트가 같은 대화 스레드 안에서 계속 작업하면서 Queuety가 검토자 결정과 수정 라운드를 관리할 수 있으므로, Neuron의 SQLChatHistory이 이 작업에 적합합니다.
Neuron 에이전트와 SQL 기반 기록
namespace App\Neuron;
use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\SystemPrompt;
final class EditorAgent extends Agent
{
public function __construct(
private readonly string $threadId,
private readonly \PDO $pdo,
) {}
protected function provider(): AIProviderInterface
{
return new Anthropic(
key: $_ENV['ANTHROPIC_API_KEY'],
model: $_ENV['ANTHROPIC_MODEL'],
);
}
protected function chatHistory(): ChatHistoryInterface
{
return new SQLChatHistory(
thread_id: $this->threadId,
pdo: $this->pdo,
table: 'neuron_chat_history',
contextWindow: 50000,
);
}
public function instructions(): string
{
return (string) new SystemPrompt(
background: [
'You are an editorial agent for long-form product briefs.',
'Apply reviewer notes without losing the original intent.',
],
);
}
}
같은 메모리 스레드로 초안을 작성하고 수정하기
namespace App\StateMachines\Actions;
use App\Neuron\EditorAgent;
use NeuronAI\Chat\Messages\UserMessage;
use Queuety\Contracts\StateAction;
final class DraftBriefAction implements StateAction
{
public function handle(array $state, ?string $event = null, array $event_payload = [], array $payload = []): array|string
{
// Queuety creates actions with `new DraftBriefAction()`, so build dependencies here.
$pdo = new \PDO($_ENV['NEURON_DB_DSN'], $_ENV['NEURON_DB_USER'], $_ENV['NEURON_DB_PASSWORD']);
$threadId = $state['neuron_thread_id'] ?? sprintf(
'brief:%d:editor',
$state['brief_id'],
);
$prompt = "Write the first draft for brief {$state['brief_id']}.";
if ('reject' === $event) {
$prompt = sprintf(
'Revise the draft using these review notes: %s',
$event_payload['reason'] ?? 'Tighten the draft and address reviewer concerns.',
);
}
$agent = new EditorAgent($threadId, $pdo);
$message = $agent->chat(
new UserMessage($prompt)
)->getMessage();
return [
'neuron_thread_id' => $threadId,
'draft_markdown' => $message->getContent(),
'review_round' => (int) ($state['review_round'] ?? 0) + 1,
'_event' => 'draft_ready',
];
}
}
Queuety 상태 머신
use Queuety\Enums\StateMachineStatus;
use Queuety\Queuety;
$machine_id = Queuety::machine('brief_review_session')
->version('brief-review-session.v1')
->state('drafting')
->action(DraftBriefAction::class)
->on('draft_ready', 'awaiting_review')
->state('awaiting_review')
->on('approve', 'completed')
->on('reject', 'drafting')
->state('completed', StateMachineStatus::Completed)
->dispatch([
'brief_id' => 42,
]);
// Later, once the draft action has finished and the machine waits in `awaiting_review`:
Queuety::machine_event(
$machine_id,
'reject',
[ 'reason' => 'Tighten the introduction and add pricing context.' ]
);
Queuety::machine_event(
$machine_id,
'approve',
[ 'reviewer' => 'editor@example.com' ]
);
첫 번째 처리에서 drafting으로 진입하고 대기 중인 작업을 실행한 다음 awaiting_review로 전환합니다.
검토자가 초안을 거부하면 머신은 다시 drafting으로 돌아갑니다. reject 이벤트 페이로드가 DraftBriefAction에 전달되므로 다음 초안은 원래의 Neuron 스레드 기록을 잃지 않고 검토자 메모를 활용할 수 있습니다.
워크플로 반복보다 이 방식이 깔끔한 이유
이 문제는 수명 주기 중심입니다. 초안은 사람의 검토를 기다리고, 검토자는 승인하거나 거부할 수 있으며, 거부되면 세션은 초안 작성으로 돌아가고, 승인되면 세션이 끝납니다. 뒤로 돌아가는 선형 워크플로보다 상태 머신으로 표현하는 편이 더 자연스럽습니다.
검토 라운드 수, 아티팩트 ID, 워크플로 ID 및 라우팅 결정 같은 오케스트레이션 정보에는 여전히 워크플로 상태가 매우 유용합니다. 이전 프롬프트, 반복 수정 내용, 초안 작성 스레드의 일부로 유지해야 할 모델 응답 같은 대화 맥락에는 Neuron 메모리가 더 적합합니다.
두 방식을 함께 사용하면 각 시스템이 가장 잘하는 일을 맡게 됩니다.
대신 워크플로로 유지할 때
검토 경로가 범위가 명확하고 실행 중심적이라면 워크플로를 사용하는 것이 좋습니다. 예를 들면 다음과 같습니다.
초안 하나를 생성하고, 승인 하나를 기다린 다음 게시합니다.
세션이 같은 상태를 여러 번 다시 거칠 수 있고 외부 이벤트가 수명 주기를 이끄는 경우에는 상태 머신을 사용하는 것이 좋습니다.