본 포스트는 'AI 에이전트 엔지니어링(AI Agent Engineering)' 시리즈의 4편입니다. 단일 에이전트 런타임의 컨텍스트 왜곡과 도구 비대화 문제를 해결하기 위해, 수퍼바이저 오케스트레이션, 동적 서브에이전트 핸드오프, HITL(Human-in-the-Loop) 승인 게이트, 그리고 트랜잭션 그래프 상태(State Graph) 관리 기법을 깊이 있게 탐구합니다.
1. 단일 에이전트의 한계와 그래프 오케스트레이션의 필요성
LLM 기반 에이전트를 프로덕션 시스템에 도입할 때 초기 PoC 단계에서는 모든 도구(Tool)와 지시사항(Instruction)을 단일 에이전트(Single Monolithic Agent) 프롬프트에 몰아넣는 방식을 취하곤 합니다. 하지만 시스템의 복잡도가 증가함에 따라 단일 에이전트는 필연적으로 구조적 한계에 부딪히게 됩니다.
- subgraph
- direction
- 모든 전문 지식 프롬프트 비대화
(System Instruction Overhead) - 30개 이상의 파편화된 도구 스키마
(Tool Schema Bloat) - 거대한 단일 컨텍스트 루프
(Context Contamination) - 환각 및 지시 이탈
(Instruction Drift & Compounding Errors) - end
- Supervisor Router
(중앙 상태 제어 노드) - Research Agent
(독립 검색 컨텍스트)
- Developer Agent
(코드 전용 격리 도구) - HITL Gate
(비동기 인터럽트) - Deploy Agent
(격리된 실행 환경) - style
1.1 단일 에이전트의 병목 현상
단일 에이전트 아키텍처가 프로덕션 스케일에서 실패하는 원인은 단순한 모델 성능 부족이 아니라 구조적 컨텍스트 오염과 도구 탐색 공간 폭발에 있습니다.
| 병목 유형 | 발생 원인 (Root Cause) | 프로덕션 임팩트 | 그래프 오케스트레이션 해결 방안 |
|---|---|---|---|
| 컨텍스트 오염 (Context Contamination) | 무분별하게 누적되는 대화 히스토리 및 도구의 Large Payload | LLM 주의력 분산으로 인한 지시 이탈(Instruction Drift) 발생 | 서브에이전트별 독립 스코프 컨텍스트 이관 및 결과 요약 합성 |
| 도구 비대화 (Tool Schema Overhead) | 단일 프롬프트에 30개 이상의 OpenAPI 도구 스키마 직접 주입 | 토큰 비용 폭증 및 유사 도구 간 오호출(Mis-selection) 확률 폭증 | 전문 노드별 도구 스키마 격리(Max –개) 배치 |
| 오류 복구 불능 (Error Compounding) | 초기 추론 실수가 루프 컨텍스트에 포함되어 계속 재인용 | 잘못된 전제를 바탕으로 환각을 반복하는 컴파운딩 오류에 매몰 | 체크포인트 롤백(Rollback) 및 조건부 에지 기반 Fallback |
단일 에이전트 루프에서 발생한 단 한 번의 잘못된 도구 입력이나 파싱 실수가 전체 컨텍스트 히스토리에 전파되면, 후속 추론 단계 전체가 오염되는 컴파운딩 오류(Compounding Errors)가 발생합니다.
1.2 절차적 워크플로우 vs 그래프 기반 상태 머신
이러한 한계를 극복하기 위해 에이전트 아키텍처는 단순 선형 워크플로우(DAG)에서 상태 그래프(Dynamic State Graph)로 진화하고 있습니다.
| 구분 | 결정론적 워크플로우 (Deterministic Workflow) | 그래프 기반 상태 머신 (Dynamic State Graph) |
|---|---|---|
| 제어 흐름 | 하드코딩된 Python 순차 실행 (A -> B -> C) | 조건부 에지(Conditional Edge) 기반 자율 라우팅 |
| 순환 및 복구 | 루프 및 동적 재시도(Retry/Fallback) 구현 복잡 | 순환 그래프(Cyclic Graph)를 통한 자율 상태 재진입 |
| 상태 불변성 | 함수 파라미터 간 단순 변수 전달 | Pydantic 스키마 기반 전역 상태 저장 및 스냅샷 관리 |
| 인터럽트 지원 | 동기식 대기로 서버 스레드 블로킹 발생 | 비동기 인터럽트(Pause) 및 체크포인트 복원(Resume) |
| 오류 복구력 | 예외 발생 시 전체 프로세스 강제 종료 | 노드 단위 격리 및 롤백 스냅샷 복구 지원 |
에이전틱 애플리케이션에서는 분기(Branching)뿐만 아니라 실패 시 이전 상태로 되돌아가는 순환(Looping)과 외부 승인을 기다리는 인터럽트(Interrupt)가 빈번히 발생하므로, 무순환 그래프(DAG) 대신 순환 상태 머신(State Graph)을 기본 아키텍처로 선택해야 합니다.
2. 수퍼바이저(Supervisor) 패턴과 동적 핸드오프(Dynamic Handoff)
멀티 에이전트 오케스트레이션에서 가장 널리 쓰이는 핵심 패턴은 수퍼바이저(Supervisor) 아키텍처입니다. 수퍼바이저는 전체 목표를 관조하며 적절한 서브에이전트에게 제어권을 위임하고, 결과를 수집하여 다음 작업을 판단합니다.
- User
- Sup
- Res
- Dev
- Gate
2.1 중앙 수퍼바이저 vs P2P Swarm 네트워크 패턴
서브에이전트 간 제어권을 넘기는 패턴은 크게 중앙 라우터 방식과 자율 핸드오프 방식으로 나뉩니다.
| 아키텍처 패턴 | 제어 흐름 구조 | 핵심 장점 | 위험 요소 & 단점 | 추천 적용 도메인 |
|---|---|---|---|---|
| 중앙 수퍼바이저 (Centralized Router) | 중앙 라우터 노드가 상태를 검토하고 서브에이전트로 분기 | • 명확한 가시성 및 추적성 • 중앙 집중식 보안/권한 제어 | • 수퍼바이저 프롬프트 성능 병목 • 라우팅 효율에 따른 홉(Hop) 증가 | 엔터프라이즈 업무 자동화, HITL 승인이 필수적인 시스템 |
| P2P Swarm (Dynamic Handoff) | 서브에이전트가 직접 handoff_to_agent() 도구 호출 | • 빠른 실행 속도 (라우터 홉 감소) • 유연한 동적 자율 협업 | • 무한 순환 호출(Infinite Loop) 위험 • 전체 상태 흐름 추적의 어려움 | 탐색적 연구, 데이터 분석, 자율적 핑퐁이 필요한 창의적 작업 |
P2P Swarm 아키텍처는 제어권 위임이 자유로운 반면 서브에이전트끼리 서로를 무한 호출하는 핑퐁 루프(Infinite Handoff Loop)가 발생하기 쉽습니다. 반드시 전역 카운터나 루프 브레이커(Loop Breaker)를 탑재해야 합니다.
2.2 상태 전달 전략: Full Transcript vs State Reduction
서브에이전트로 제어권을 이전할 때, 대화 전체 히스토리를 넘기면 단일 에이전트의 컨텍스트 오염 문제가 그대로 재발합니다. 프로덕션 에이전트 엔진은 상태 요약 및 축소(State Reduction) 방식을 채택합니다.
| 전달 전략 | 전달 데이터 스코프 | 토큰 효율성 | 모델 주의력 | 오염 방지 능력 |
|---|---|---|---|---|
| Full Transcript | 전체 대화 히스토리 및 이전 도구 실행 결과 일체 | 토큰 소모 극대화 (비용 폭증) | 불필요 패치에 주의력 분산 | 초기 오류가 계속 전파됨 |
| State Reduction | 정제된 Pydantic State (필수 목표 + 요약 결과만 전달) | 최고 수준의 토큰 절감 | 작업 필수 맥락에 100% 집중 | 서브에이전트 간 완벽 격리 |
# 상태 축소(State Reduction) 메커니즘 예시class GlobalState(BaseModel): task_id: str original_goal: str plan_steps: list[str] research_summary: Optional[str] = None generated_code: Optional[str] = None approval_status: str = "PENDING" messages: list[BaseMessage] # 전역 대화 목록
수퍼바이저가 DeveloperAgent를 호출할 때는 GlobalState.messages 전체가 아니라, original_goal과 research_summary만을 정제하여 프롬프트로 주입함으로써 서브에이전트의 집중도를 최대로 끌어올립니다.
서브에이전트에 격리된 컨텍스트(Scoped Context)를 넘겨주고 작업 결과는 구조화된 상태 갱신값(State Mutation)으로 리턴받아 병합하는 방식이 토큰 비용 절감과 정밀도 향상의 핵심입니다.
3. Human-in-the-Loop (HITL) 인터럽트와 승인 게이트
에이전트가 외부 API 호출, DB 레코드 삭제, 금융 결제, 또는 프로덕션 서버 배포와 같이 가역적이지 않은(Irreversible) 고위험 액션을 수행할 때는 반드시 HITL(Human-in-the-Loop) 승인 게이트가 개입해야 합니다.
3.1 HITL Approval Gate 상태 전환 다이어그램:
- state
- Idle
- Analyzing
- SubAgentExecution
- EvaluatingRisk
- ExecutingAction
- InterruptedPending
- CheckpointSaved
- WaitingHumanInput
- Approved
- Rejected
- TimedOut
- RePlanning
- RollbackState
- Completed
3.2 비동기 인터럽트 및 4단계 실행 라이프사이클
HITL을 성공적으로 구현하려면 그래프 실행 엔진이 동기식 대기가 아닌 비동기 중단(Pause)과 재개(Resume) 라이프사이클을 지원해야 합니다.
| 실행 단계 | 상태 전환 | Checkpoint 저장 내용 | 시스템 스레드 동작 |
|---|---|---|---|
| 1. 위험 감지 | NONE → PENDING | 현재 AgentState snapshot을 DB에 저장 | 위험 노드 실행 직전 비동기 중단 |
| 2. 비동기 대기 | PENDING 유지 | DB 스냅샷 저장 상태 유지 (is_interrupted=True) | HTTP 서버 응답 반환 후 스레드 해제 |
| 3. 인간 결정 주입 | PENDING → APPROVED / REJECTED | 승인 상태 및 feedback 속성 업데이트 | Webhook / API 요청 수신 시 트리거 |
| 4. 그래프 재개 | APPROVED → NONE | 복원된 스냅샷 기반 다음 노드 실행 | 비동기 스트림 재연결 및 실행 완수 |
HITL 인터럽트 구현에서 핵심은 웹 서버 스레드가 비동기로 동작하여 리소스를 블로킹하지 않아야 하며, 무기한 대기 상태에서도 DB에 저장된 체크포인트를 통해 서버 재시작 후 안전하게 복구할 수 있어야 합니다.
HITL Resume 엔드포인트는 악의적인 사용자의 상태 조작 공격 대상이 될 수 있습니다. 반드시 RBAC(Role-Based Access Control) 인가 검증과 수정 불가능한 감사 로그(Audit Log)를 남겨야 합니다.
4. 파이썬 트랜잭션 상태 관리와 실시간 스트리밍 인터페이스
멀티 에이전트 상태 머신은 복수의 비동기 노드가 동시 다발적으로 상태를 읽고 쓰는 환경이므로, 트랜잭션 격리와 실시간 스트리밍(Streaming) 구조가 필수적입니다.
4.1 Async Checkpoint 상태 영속화 시퀀스
체크포인터는 그래프의 각 노드 실행 직전/직후의 상태를 영속화하여 비동기 중단 및 장애 발생 시의 복구를 보장합니다.
- Engine
- Checkpointer
- DB
- Client
- Admin
- Node
4.2 이벤트 기반 스트리밍 (Event-driven Streaming)
사용자 경험(UX) 측면에서 멀티 에이전트의 길고 복잡한 연산 과정을 투명하게 보여주기 위해 4가지 레벨의 스트리밍 이벤트를 제공해야 합니다.
| 스트림 이벤트 유형 | 발생 시점 | 주요 페이로드 구성 | Front-end UX 활용 |
|---|---|---|---|
NODE_START | 새 상태 노드 진입 시 | node_name, history_len, timestamp | 진행 상황 프로그레스 바 및 에이전트 변경 알림 |
NODE_END | 상태 노드 연산 완료 시 | node_name, next_node, state_diff | 단계 완수 체크마크 표시 및 다음 단계 안내 |
INTERRUPT | HITL Gate 감지 시 | patch_to_review, approval_status | 사용자 승인 Modal 팝업 및 피드백 입력창 노출 |
STREAM_CHUNK | LLM 토큰 생성 시 | delta_text, agent_id | 실시간 타이핑 텍스트 모나코 에디터 렌더링 |
상태 변경의 불변성(Immutability)을 보장하기 위해 노드 간 이동 시에는 기존 State를 직접 수정하기보다 복사본을 만들어 갱신(State Mutation) 후 저장하는 트랜잭션 패턴을 사용해야 합니다.
5. 프로덕션 레벨 파이썬 구현: 수퍼바이저 그래프 & HITL 승인 엔진
다음은 Pydantic v2와 Python asyncio를 활용하여 외부 의존성 없이 상태 그래프, 수퍼바이저 동적 라우팅, HITL 인터럽트 게이트, 체크포인트 메모리 스토어, 비동기 이벤트 스트리밍을 완전히 구현한 프로덕션 레벨의 파이썬 예제 코드입니다.
본 코드는 서드파티 프레임워크 없이 Python 순수 코드로 작성되어 LangGraph, AutoGen, CrewAI 등 하부 엔진의 인터럽트 및 스냅샷 복원 동작 원리를 직관적으로 이해할 수 있도록 설계되었습니다.
"""Production-grade Multi-Agent State Graph Orchestrator with HITL GateFilename: agent_orchestrator.py""" import asynciofrom enum import Enumfrom typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List, Optionalfrom pydantic import BaseModel, Field # -----------------------------------------------------------------------# 1. State Models & Types# ----------------------------------------------------------------------- class ApprovalStatus(str, Enum): NONE = "NONE" PENDING = "PENDING" APPROVED = "APPROVED" REJECTED = "REJECTED" class AgentState(BaseModel): thread_id: str user_query: str current_node: str = "supervisor" next_node: Optional[str] = None # Domain specific data research_notes: List[str] = Field(default_factory=list) generated_patch: Optional[str] = None # Control & HITL Flags is_interrupted: bool = False approval_status: ApprovalStatus = ApprovalStatus.NONE hitl_feedback: Optional[str] = None execution_history: List[str] = Field(default_factory=list) def log(self, step: str) -> None: self.execution_history.append(step) class StreamEvent(BaseModel): event_type: str # "NODE_START", "NODE_END", "INTERRUPT", "STREAM_CHUNK" node_name: str payload: Dict[str, Any] # -----------------------------------------------------------------------# 2. State Checkpointer & Persistence Store# ----------------------------------------------------------------------- class AsyncCheckpointer: def __init__(self) -> None: self._store: Dict[str, AgentState] = {} async def save(self, state: AgentState) -> None: # Deep copy simulated via model_copy self._store[state.thread_id] = state.model_copy(deep=True) async def load(self, thread_id: str) -> Optional[AgentState]: state = self._store.get(thread_id) if state: return state.model_copy(deep=True) return None # -----------------------------------------------------------------------# 3. Subagent Nodes & Logic# ----------------------------------------------------------------------- async def supervisor_node(state: AgentState) -> AgentState: state.log("Supervisor: Assessing task status...") if not state.research_notes: state.next_node = "researcher" elif state.generated_patch is None: state.next_node = "developer" elif state.approval_status == ApprovalStatus.NONE: state.next_node = "hitl_gate" elif state.approval_status == ApprovalStatus.APPROVED: state.next_node = "deployer" elif state.approval_status == ApprovalStatus.REJECTED: state.next_node = "developer" # Re-plan/Re-code on rejection else: state.next_node = "end" return state async def researcher_node(state: AgentState) -> AgentState: state.log("Researcher: Gathering system requirements and codebase context...") await asyncio.sleep(0.3) # Async API work simulation state.research_notes.append("Found legacy payment gateway route requiring OAuth2 update.") state.research_notes.append("Dependency check: Auth SDK v2.4 compatibility confirmed.") state.next_node = "supervisor" return state async def developer_node(state: AgentState) -> AgentState: state.log("Developer: Drafting code patch...") await asyncio.sleep(0.4) if state.approval_status == ApprovalStatus.REJECTED: # Refine code based on HITL feedback state.generated_patch = f"PATCH v2 (Refactored based on feedback: {state.hitl_feedback})" state.approval_status = ApprovalStatus.NONE # Reset for re-approval else: state.generated_patch = "PATCH v1: Fix OAuth2 Authorization Header formatting in payment_client.py" state.next_node = "supervisor" return state async def hitl_approval_gate_node(state: AgentState) -> AgentState: state.log("HITL Gate: Checking safety boundary for code deployment...") if state.approval_status == ApprovalStatus.NONE: state.log("HITL Gate: High-risk action detected (Deploy)! Interrupting graph execution.") state.is_interrupted = True state.approval_status = ApprovalStatus.PENDING state.next_node = "supervisor" elif state.approval_status in (ApprovalStatus.APPROVED, ApprovalStatus.REJECTED): state.is_interrupted = False state.next_node = "supervisor" return state async def deployer_node(state: AgentState) -> AgentState: state.log("Deployer: Applying patch to production environment...") await asyncio.sleep(0.5) state.log(f"Deployer: Successfully deployed -> {state.generated_patch}") state.next_node = "end" return state # -----------------------------------------------------------------------# 4. State Graph Orchestrator Engine# ----------------------------------------------------------------------- class StateGraphEngine: def __init__(self, checkpointer: AsyncCheckpointer) -> None: self.nodes: Dict[str, Callable[[AgentState], Awaitable[AgentState]]] = {} self.checkpointer = checkpointer def add_node(self, name: str, func: Callable[[AgentState], Awaitable[AgentState]]) -> None: self.nodes[name] = func async def run_stream(self, thread_id: str, initial_query: Optional[str] = None) -> AsyncGenerator[StreamEvent, None]: # Load or initialize state state = await self.checkpointer.load(thread_id) if state is None: if not initial_query: raise ValueError("Initial query required for new thread.") state = AgentState(thread_id=thread_id, user_query=initial_query) while state.current_node != "end": current_name = state.current_node node_func = self.nodes.get(current_name) if not node_func: raise KeyError(f"Node '{current_name}' is not registered in state graph.") yield StreamEvent( event_type="NODE_START", node_name=current_name, payload={"history_len": len(state.execution_history)} ) # Execute Node state = await node_func(state) # Check for HITL Interrupt if state.is_interrupted: await self.checkpointer.save(state) yield StreamEvent( event_type="INTERRUPT", node_name=current_name, payload={ "message": "Graph paused for human approval.", "patch_to_review": state.generated_patch, "approval_status": state.approval_status.value } ) return # Exit stream, waiting for external resume trigger yield StreamEvent( event_type="NODE_END", node_name=current_name, payload={"next_node": state.next_node} ) state.current_node = state.next_node await self.checkpointer.save(state) async def resume_with_approval(self, thread_id: str, approved: bool, feedback: Optional[str] = None) -> AsyncGenerator[StreamEvent, None]: state = await self.checkpointer.load(thread_id) if not state or not state.is_interrupted: raise RuntimeError(f"Thread '{thread_id}' is not in an interrupted state.") # Inject Human Decision state.approval_status = ApprovalStatus.APPROVED if approved else ApprovalStatus.REJECTED state.hitl_feedback = feedback state.is_interrupted = False state.log(f"Human Decision Injected: Approved={approved}, Feedback={feedback}") await self.checkpointer.save(state) # Resume execution stream async for event in self.run_stream(thread_id): yield event # -----------------------------------------------------------------------# 5. Verification & Execution Walkthrough# ----------------------------------------------------------------------- async def main() -> None: checkpointer = AsyncCheckpointer() engine = StateGraphEngine(checkpointer) # Register Graph Nodes engine.add_node("supervisor", supervisor_node) engine.add_node("researcher", researcher_node) engine.add_node("developer", developer_node) engine.add_node("hitl_gate", hitl_approval_gate_node) engine.add_node("deployer", deployer_node) thread_id = "tx_session_9901" print("=== [1 Phase: Execution until HITL Interrupt] ===") async for event in engine.run_stream(thread_id, initial_query="Update payment API route"): print(f"[{event.event_type}] Node: {event.node_name} -> {event.payload}") # Inspect state at interrupt point paused_state = await checkpointer.load(thread_id) print(f"\n[System Status] Graph Interrupted? {paused_state.is_interrupted}") print(f"[Review Target] Proposed Code: {paused_state.generated_patch}") print("\n=== [2 Phase: Simulated Human Rejection & Feedback Loop] ===") async for event in engine.resume_with_approval(thread_id, approved=False, feedback="Use Bearer prefix for Auth header"): print(f"[{event.event_type}] Node: {event.node_name} -> {event.payload}") print("\n=== [3 Phase: Final Human Approval & Deployment] ===") async for event in engine.resume_with_approval(thread_id, approved=True): print(f"[{event.event_type}] Node: {event.node_name} -> {event.payload}") final_state = await checkpointer.load(thread_id) print("\n=== [Final Execution Log] ===") for idx, log_entry in enumerate(final_state.execution_history, 1): print(f"{idx}. {log_entry}") if __name__ == "__main__": asyncio.run(main())
6. Mermaid 아키텍처 & 시퀀스 다이어그램
6.1 수퍼바이저 & 서브에이전트 그래프 오케스트레이션
시스템 전체의 상태 노드 전환과 수퍼바이저 제어 구조를 시각화한 상태 그래프 다이어그램입니다.
- Start
- Supervisor
- Researcher
- Developer
- Node: HITL Approval Gate
- CheckpointStore
- Deployer
- End
- classDef
- class
6.2 HITL 인터럽트 및 비동기 재개(Resume) 시퀀스
외부 웹/애플리케이션 인터페이스와 에이전트 그래프 엔진 간의 비동기 인터럽트 및 상태 복원(Resume) 과정을 나타낸 시퀀스 다이어그램입니다.
- Engine
- DB
- API
- Inspector
- Node
7. 멀티 에이전트 오케스트레이션 운영 체크리스트
프로덕션 환경에 수퍼바이저 및 멀티 에이전트 오케스트레이션을 배치하기 전에 반드시 검증해야 할 체크리스트입니다.
| 영역 | 항목 | 검증 내용 | 심각도 |
|---|---|---|---|
| 아키텍처 | 서브에이전트 단일 책임 | 각 서브에이전트의 역할과 보유 도구가 명확히 격리되어 있는가? (도구 10개 이하 권장) | High |
| 상태 관리 | 상태 축소 (State Reduction) | 수퍼바이저 및 서브에이전트 이동 시 불필요한 전체 대화 히스토리를 요약/제거하는가? | Critical |
| 상태 관리 | 체크포인트 영속성 | 프로세스 재부팅 시에도 대기 중인 인터럽트 상태를 DB에서 안전하게 복원할 수 있는가? | Critical |
| HITL 보안 | 인터럽트 롤백 보장 | 승인 거부(Rejected) 시 이전 안전 노드로 복귀하여 재작업(Re-plan)을 수행하는 흐름이 존재하는가? | Critical |
| HITL 보안 | 인가(Authorization) 게이트 | 승인 신호(Resume Trigger)를 보내는 주체의 권한이 RBAC 시스템과 연동되어 검증되는가? | Critical |
| 운영 / Observability | 무한 루프 감지 (Loop Breaker) | 서브에이전트 간 핸드오프 횟수가 최대 이터레이션(e.g., Max 15 steps)을 초과할 때 강제 종료되는가? | High |
| 운영 / Observability | 이벤트 스트리밍 추적 | Node Transition, Tool Call, LLM Streaming이 OpenTelemetry/LangSmith 트레이스와 동기화되는가? | Medium |
프로덕션 멀티 에이전트 오케스트레이션 구축 시 체크포인트 영속성과 무한 루프 방지(Loop Breaker)가 구비되지 않은 상태로 실서버 배포를 진행할 경우, 과도한 LLM API 호출 비용 발생 및 제어 불능 상태에 직면할 위험이 높습니다.
마무리하며
단일 에이전트의 한계를 넘어선 그래프 기반 멀티 에이전트 오케스트레이션은 복잡한 엔터프라이즈 도메인 문제를 해결하기 위한 표준 아키텍처로 자리잡았습니다. 핵심은 단지 에이전트의 개수를 늘리는 것이 아니라, 명확한 전역 상태 스키마, 수퍼바이저 기반의 정교한 라우팅, 격리된 서브에이전트 컨텍스트, 그리고 안전성을 보장하는 HITL 인터럽트 게이트를 설계하는 것입니다. 다음 5편에서는 에이전트의 확장성과 도구 생태계 표준인 와 에이전트 간 통신 표준인 A2A(Agent-to-Agent) 아키텍처를 상세히 다룹니다.

댓글
GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.