Neuron 워크플로우를 사용한 'Jarvis' 에이전트 메시 구축하기
요약
본 글은 거대한 단일 에이전트 대신, 작은 역할별 에이전트들이 'Neuron AI' 워크플로우를 통해 협력하는 메시(mesh) 아키텍처를 제안합니다. 이 방식은 타입화된 이벤트와 멱등성 시그널 버스를 사용하여 비즈니스 프로세스의 투명성과 안정성을 높입니다.
핵심 포인트
- 워크플로우가 시퀀스 소유권을 가져야 함 (에이전트 X)
- 타입화된 이벤트(typed events)로 에이전트 간 라우팅 구현
- 멱등성(idempotency)을 보장하는 시그널 및 지식 저장소 사용
- LLM/스토리지 백엔드 교체 시 워크플로우 코드 변경 최소화
타입화된 이벤트,멱등성(idempotent) 시그널 버스 및 경계가 있는 감시견(watchdog)을 통해 여러 PHP 에이전트들이 스케줄링이나 전송 로직을 프레임워크 내부에 포함하지 않고 협력할 수 있게 합니다.
저는 작은 지주회사를 운영하고 있으며, 많은 사람들처럼 저도 'Jarvis'를 원했습니다. 즉, 아침 브리핑 자료를 준비하고, 운영 현황을 감시하며, 무언가 작동을 멈추면 알려주는 일련의 에이전트들입니다. 거대한 프롬프트 하나를 가진 하나의 거대한 에이전트를 사용하는 대신, 저는 메시(mesh) 방식을 시도했습니다. 이는 작은, 역할별 에이전트들이 Neuron AI 워크플로우에 의해 조정되는 방식입니다.
그 결과는 PR #683으로 제안된 간결한 예제입니다 (examples/agent/jarvis-mesh.php 및 docs/jarvis-mesh.md). 본 게시물에서는 이 설계 선택 사항들을 설명합니다.
투명성 참고: 이 프로토타입은 AI 코딩 지원을 받아 구축되었으며, 실제 운영 코드(production code)가 아닌 시작점으로 간주해야 합니다. PR은 아직 열려 있으며 검토되거나 병합되지 않았습니다.
아이디어: 워크플로우가 시퀀스를 소유하게 하라, 에이전트들이 소유하지 않게 하라
세 개의 에이전트는 각각 하나의 임무를 가집니다:
- Research: 당일 출처 기반의 주요 내용을 준비합니다.
- Operations: 연구 결과를 바탕으로 현황과 위험 요소를 요약합니다.
- Briefing: 두 가지 정보를 모두 사용하여 최종 일일 브리핑을 작성합니다.
이들은 서로를 호출하지 않습니다. Neuron Workflow는 **타입화된 이벤트(typed events)**를 사용하여 그들 사이를 라우팅합니다: BriefingStarted -> ResearchCompleted -> OperationsCompleted -> BriefingComposed. 각 노드는 하나의 이벤트 타입을 받아 다음 이벤트를 반환하므로, 비즈니스 프로세스의 그래프가 타입 시그니처만으로 읽을 수 있게 됩니다.
프레임워크 코드 대신 어댑터 사용하기
이 예제는 애플리케이션이 구현해야 하는 네 개의 작은 인터페이스를 의도적으로 정의합니다:
JarvisSignalBus: 타입화된 시그널을 발행하며, **ID 기반으로 멱등성(idempotent)**을 보장합니다.JarvisKnowledgeStore: 에이전트들 간의 공유 노트이며, 키(key) 기반으로 멱등성을 보장합니다.JarvisHeartbeatStore: 각 에이전트가 마지막으로 감지된 시간입니다.JarvisAgentFactory: 역할별 및 스레드별로 새로 구성된AgentInterface를 반환합니다.
이들은 WorkflowResources 객체로 워크플로우에 전달되며, 각 실행 세그먼트마다 재구축됩니다. Provider, tools, instructions 및 message store는 모두 factory 내에 존재하므로, LLM이나 스토리지 백엔드를 교체할 때도 워크플로우 코드가 변경되지 않습니다.
리플레이(Replay)에서 살아남기
워크플로우는 중단되었다가 재개될 수 있으므로, 모든 부작용(side effect)은 두 번 실행되는 것을 견뎌야 합니다. 다음 두 가지 메커니즘이 도움을 줍니다:
- 에이전트 호출은 노드의
memoize()를 거치기 때문에, 중단된 실행이 재개될 때 완료된 에이전트 호출이 반복되지 않습니다. - 시그널 및 지식 쓰기는 안정적인 키(예:
"{workflowId}/research.completed")를 사용하므로, 반복적인 발행은 중복을 생성하지 않습니다.
문서에도 명시된 솔직한 한계가 있습니다: 메모이제이션(memoization)은 외부 부작용을 정확히 한 번만 발생하도록 만들 수 없습니다. Idempotent adapter는 사용자 책임입니다.
오래된 에이전트를 위한 제한된 워치독(Watchdog)
두 번째 워크플로우인 WatchdogWorkflow는 **단일 패스 스윕(single-pass sweep)**입니다. 사용자의 스케줄러(cron, 큐 등 이미 사용하는 것)가 에이전트 목록, 타임스탬프 및 오래됨 임계값과 함께 매 스윕마다 새로운 실행을 시작합니다. 각 에이전트에 대해 하나의 노드가 마지막 심장 박동(heartbeat)을 확인하고, 만약 그것이 오래되었거나 누락된 경우, 메모를 작성하고 해당 에이전트와 마지막으로 관찰된 시간을 기반으로 파생된 키 아래에 agent.heartbeat.stale 시그널을 발행합니다. 따라서 같은 스윕을 두 번 실행해도 동일한 경고가 한 번만 발생합니다.
이 워크플로우는 하나의 단계와 60초의 임대 시간(lease)으로 제한되므로, 무한 루프를 돌 수 없습니다. 스케줄링, 심장 박동 생성, 알림 전송 및 모니터링 정책은 애플리케이션에 남아 있습니다.
왜 워크플로우 시그널을 버스로 사용하지 않는가?
Neuron의 Workflow::signal()은 _현재 일치 대기(current matching wait)_를 처리합니다. 이것은 브로드캐스트도 아니고 큐도 아닙니다. 독립적인 에이전트 간의 팬아웃(fan-out)을 위해서는 실제 공유 버스(shared bus)가 필요하며, 이것이 예시에서 이를 애플리케이션 통합 경계로 다루는 이유입니다. 제가 정한 규칙은 다음과 같습니다: 운송 및 스케줄링은 워크플로우 노드 외부에서; 비즈니스 시퀀싱 및 상태는 그래프 내부에서.
연결하기(Wiring it up)
애플리케이션에서 어댑터와 영속성 백엔드를 제공한 다음, 다음을 실행합니다:
$workflow = (new DailyBriefingWorkflow($date))
->setPersistence($workflowPersistence)
->setResources($resourcesFactory);
...
워커들이 겹칠 수 있는 경우 버스(bus)와 지식 저장소(knowledge store)에 대해 내구성이 있고 공유 가능한 구현체, 그리고 다중 프로세스 안전성(multi-process-safe)을 갖춘 영속성 백엔드를 사용하세요.
테스트 방법
시뮬레이션 에이전트 및 인메모리 어댑터(실제 LLM 없음, 실제 데이터베이스 없음)를 사용하여 Neuron 4.1에 대해 예제를 실행했습니다. 브리핑은 처음부터 끝까지 실행되었고; 중간에 실패한 에이전트는 완료된 리서치 단계를 반복하지 않고 재개되었습니다; 그리고 워치독(watchdog)은 오래된 에이전트에 대한 경고를 하나, 누락된 에이전트에 대한 경고를 하나 발생시키고, 정상적인 것은 무시했으며, 두 번째 스윕에서 경고가 중복되지 않았습니다. 저는 전체 Neuron 테스트 스위트나 PHPStan을 실행하지 않았으며, 이미 완료된 워크플로우를 동일한 ID로 다시 실행하면 에이전트들이 다시 호출됩니다(신호는 여전히 중복 제거됩니다).
다음에 구축할 것들
- 각 에이전트 워커 내부에 실제 심장 박동 생성기(heartbeat producer).
agent.heartbeat.stale의 소비자로서 알림 전송(채팅, 이메일).- 인메모리 페이크를 사용한 어댑터에 대한 적절한 단위 테스트(unit tests).
Jules Langlois
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기