비동기 서브에이전트
비동기 서브에이전트 (Async subagents)
슈퍼바이저 에이전트가 백그라운드 작업을 쏘아 올리고, 그 결과를 기다리지 않고 바로 사용자와 대화를 계속할 수 있게 해주는 게 비동기 서브에이전트예요. 작업이 실행되는 동안 진행 상황을 확인하고, 추가 지시를 보내고, 언제든 작업을 취소할 수 있어요. 이 문서는 동기적으로 실행되며 슈퍼바이저를 블로킹하는 일반 subagents의 확장판으로, 오래 걸리거나 병렬화 가능하거나 도중에 방향을 바꿔야 하는 작업에 적합해요.
비동기 서브에이전트는 Agent Protocol을 구현한 어떤 서버와도 통신해요. LangSmith Deployments를 쓰거나 Agent Protocol 호환 서버를 직접 호스팅할 수도 있어요. 각 서브에이전트는 슈퍼바이저와 독립적으로 실행되고, 슈퍼바이저는 SDK를 통해 작업을 시작·확인·갱신·취소해요.
출처: 공식문서
언제 비동기 서브에이전트를 쓸까
| 차원 | 동기 서브에이전트 | 비동기 서브에이전트 |
|---|---|---|
| 실행 모델 | 슈퍼바이저가 서브에이전트 완료까지 블로킹 | 작업 ID를 즉시 반환, 슈퍼바이저는 계속 진행 |
| 동시성 | 병렬이지만 블로킹 | 병렬 + 비블로킹 |
| 작업 중 업데이트 | 불가능 | update_async_task로 추가 지시 전송 |
| 취소 | 불가능 | cancel_async_task로 실행 중인 작업 취소 |
| 상태 | Stateless — 호출 간 영구 상태 없음 | Stateful — 자체 스레드에 상태 유지 |
| 적합한 경우 | 결과를 기다려야 하는 작업 | 채팅에서 상호작용으로 관리되는 오래 걸리는 복잡한 작업 |
비동기 서브에이전트 구성하기
비동기 서브에이전트는 AsyncSubAgent 스펙 목록으로 정의하고, 각각이 Agent Protocol 서버를 가리키게 해요.
from deepagents import AsyncSubAgent, create_deep_agent
async_subagents = [
AsyncSubAgent(
name="researcher",
description="Research agent for information gathering and synthesis",
graph_id="researcher",
# url이 없으면 → ASGI transport (같은 배포에 함께 배포)
),
AsyncSubAgent(
name="coder",
description="Coding agent for code generation and review",
graph_id="coder",
# url="https://coder-deployment.langsmith.dev" # 선택: 원격용 HTTP transport
),
]
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
subagents=async_subagents,
)
| 필드 | 타입 | 설명 |
|---|---|---|
name |
str |
필수. 고유 식별자. 슈퍼바이저가 작업 시작 시 사용해요. |
description |
str |
필수. 이 서브에이전트가 하는 일. 슈퍼바이저가 어느 에이전트에 위임할지 결정할 때 사용해요. |
graph_id |
str |
필수. Agent Protocol 서버의 그래프 ID(또는 assistant ID). LangGraph 기반 배포라면 langgraph.json에 등록된 그래프와 일치해야 해요. |
url |
str |
선택. 생략하면 ASGI transport(인프로세스), 설정하면 원격 Agent Protocol 서버로 HTTP transport를 사용해요. |
headers |
dict[str, str] |
선택. 원격 서버 요청에 추가할 헤더. 자체 호스팅 Agent Protocol 서버의 커스텀 인증에 사용해요. |
LangGraph 기반 배포라면 co-deployed 설정을 위해 모든 그래프를 같은 langgraph.json에 등록해요.
{
"graphs": {
"supervisor": "./src/supervisor.py:graph",
"researcher": "./src/researcher.py:graph",
"coder": "./src/coder.py:graph"
}
}
비동기 서브에이전트 도구 사용하기
비동기 서브에이전트를 구성하면 Deep Agents 스택에 포함된 AsyncSubAgentMiddleware가 슈퍼바이저에게 다섯 가지 도구를 제공해요.
| 도구 | 용도 | 반환 |
|---|---|---|
start_async_task |
새 백그라운드 작업 시작 | 작업 ID (즉시) |
check_async_task |
작업의 현재 상태와 결과 조회 | 상태 + 결과(완료 시) |
update_async_task |
실행 중인 작업에 새 지시 전송 | 확인 + 갱신된 상태 |
cancel_async_task |
실행 중인 작업 중지 | 확인 |
list_async_tasks |
추적 중인 모든 작업과 실시간 상태 나열 | 모든 작업 요약 |
슈퍼바이저의 LLM은 이 도구들을 다른 도구처럼 호출해요. 미들웨어가 스레드 생성, run 관리, 상태 영속화를 자동으로 처리해요.
라이프사이클 이해하기
- Launch: 서버에 새 스레드를 만들고, 작업 설명을 입력으로 run을 시작하며, 스레드 ID를 작업 ID로 반환해요. 슈퍼바이저는 이 ID를 사용자에게 알려주고 완료를 폴링하지 않아요.
- Check: 현재 run 상태를 조회해요. 성공했다면 스레드 상태를 가져와 서브에이전트의 최종 출력을 추출하고, 아직 실행 중이면 사용자에게 그렇게 알려요.
- Update: 같은 스레드에 인터럽트 멀티태스크 전략으로 새 run을 만들어요. 이전 run은 인터럽트되고, 서브에이전트는 전체 대화 기록 + 새 지시로 다시 시작해요. 작업 ID는 그대로 유지돼요.
- Cancel: 서버에서
runs.cancel()을 호출하고 작업을"cancelled"로 표시해요. - List: 추적 중인 모든 작업을 순회해요. 종료되지 않은 작업은 서버에서 실시간 상태를 병렬로 가져오고, 종료 상태(
success,error,cancelled)는 캐시에서 반환해요.
상태 관리 이해하기
작업 메타데이터는 슈퍼바이저 그래프의 전용 상태 채널(async_tasks)에 메시지 기록과 분리해 저장돼요. 이게 중요한 이유는 딥 에이전트가 컨텍스트 윈도우가 차면 메시지 기록을 컴팩트하기 때문이에요. 작업 ID가 도구 메시지에만 있었다면 컴팩트 중에 유실됐을 거예요. 전용 채널 덕분에 슈퍼바이저는 요약이 여러 번 반복된 후에도 list_async_tasks로 작업을 항상 기억할 수 있어요.
추적되는 각 작업은 작업 ID, 에이전트 이름, 스레드 ID, run ID, 상태, 타임스탬프(created_at, last_checked_at, last_updated_at)를 기록해요.
전송 방식 선택하기
ASGI transport (co-deployed)
서브에이전트 스펙에서 url 필드를 생략하면 LangGraph SDK는 ASGI transport를 사용해요 — SDK 호출이 HTTP가 아니라 인프로세스 함수 호출로 라우팅돼요. LangGraph 기반 배포라면 두 그래프가 같은 langgraph.json에 등록돼 있어야 해요. ASGI transport는 네트워크 지연을 없애고 추가 인증 설정이 필요 없어요. 서브에이전트는 여전히 자체 상태를 가진 별도 스레드로 실행돼요. 권장 기본값이에요.
HTTP transport (remote)
url 필드를 추가하면 SDK 호출이 네트워크를 통해 원격 Agent Protocol 서버로 가는 HTTP transport로 전환돼요.
from deepagents import AsyncSubAgent
AsyncSubAgent(
name="researcher",
description="Research agent",
graph_id="researcher",
url="https://my-research-deployment.langsmith.dev",
)
LangGraph 배포의 인증은 환경 변수 LANGSMITH_API_KEY(또는 LANGGRAPH_API_KEY)를 사용하는 LangGraph SDK가 처리해요. 자체 호스팅 Agent Protocol 서버는 다른 인증 방식을 쓸 수 있어요.
서브에이전트가 독립 스케일링, 다른 리소스 프로필, 또는 다른 팀에서 관리해야 할 때 HTTP transport를 사용해요.
배포 토폴로지 선택하기
- 단일 배포 (Single deployment): ASGI transport를 사용해 모든 에이전트를 같은 서버에 함께 배포해요. LangGraph 기반이면 모든 그래프를 하나의
langgraph.json에 등록해요. 권장하는 시작점이에요 — 서버 하나만 관리하고 에이전트 간 네트워크 지연이 0이에요. - 분리 배포 (Split deployment): 슈퍼바이저는 한 서버에, 서브에이전트는 HTTP transport로 다른 서버에 배포해요. 서브에이전트가 다른 컴퓨팅 프로필이나 독립 스케일링이 필요할 때 사용해요.
- 하이브리드 (Hybrid): 일부 서브에이전트는 ASGI로 함께 배포하고, 일부는 HTTP로 원격 배포해요.
from deepagents import AsyncSubAgent
async_subagents = [
AsyncSubAgent(
name="researcher",
description="Research agent",
graph_id="researcher",
# url이 없으면 → ASGI (co-deployed)
),
AsyncSubAgent(
name="coder",
description="Coding agent",
graph_id="coder",
url="https://coder-deployment.langsmith.dev",
# url이 있으면 → HTTP (remote)
),
]
모범 사례 (Best practices)
로컬 개발용 워커 풀 크기 잡기
langgraph dev로 로컬 실행할 때는 동시 서브에이전트 run을 수용하도록 워커 풀을 늘려요. 각 활성 run은 워커 슬롯 하나를 차지해요. 슈퍼바이저 1 + 서브에이전트 3 동시 작업이면 4개 슬롯이 필요해요. 부족하면 런치가 대기열에 밀려요.
langgraph dev --n-jobs-per-worker 10
명확한 서브에이전트 설명 작성하기
슈퍼바이저는 description을 보고 어느 서브에이전트를 띄울지 결정해요. 구체적이고 행동 지향적으로 작성해요.
from deepagents import AsyncSubAgent
# 좋은 예
AsyncSubAgent(
name="researcher",
description="Conducts in-depth research using web search. Use for questions requiring multiple searches and synthesis.",
graph_id="researcher",
)
from deepagents import AsyncSubAgent
# 나쁜 예
AsyncSubAgent(
name="helper",
description="helps with stuff",
graph_id="helper",
)
스레드 ID로 추적하기
LangGraph 기반 배포에서는 모든 비동기 서브에이전트 run이 표준 LangGraph run이라 LangSmith에서 완전히 보여요. 슈퍼바이저의 트레이스에는 launch, check, update, cancel, list 도구 호출이 나타나요. 각 서브에이전트 run은 별도 트레이스로 보이고 스레드 ID로 연결돼요. 스레드 ID(작업 ID)를 사용해 슈퍼바이저 오케스트레이션 트레이스와 서브에이전트 실행 트레이스를 연관지어요.
트러블슈팅 (Troubleshooting)
슈퍼바이저가 런치 직후 바로 폴링
문제: 런치 직후 슈퍼바이저가 check를 반복 호출해서 비동기 실행이 블로킹이 돼버려요.
해결: 미들웨어가 이를 막는 시스템 프롬프트 규칙을 주입해요. 그래도 폴링이 계속되면 슈퍼바이저 시스템 프롬프트에서 동작을 강화해요.
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
system_prompt="""...your instructions...
After launching an async subagent, ALWAYS return control to the user.
Never call check_async_task immediately after launch.""",
subagents=async_subagents,
)
슈퍼바이저가 오래된 상태를 보고
문제: 슈퍼바이저가 새 check 호출 대신 대화 기록 이전의 작업 상태를 참조해요.
해결: 미들웨어 프롬프트는 모델에게 "대화 기록의 작업 상태는 항상 오래된 것"이라고 알려줘요. 그래도 발생하면 상태를 보고하기 전에 항상 check나 list를 호출하라는 명시적 지시를 추가해요.
작업 ID 조회 실패
문제: 슈퍼바이저가 작업 ID를 자르거나 다시 포맷해서 check나 cancel이 실패해요.
해결: 미들웨어 프롬프트는 모델에게 항상 전체 작업 ID를 쓰도록 지시해요. 잘림이 계속되면 보통 모델 특유의 문제라 다른 모델을 쓰거나 시스템 프롬프트에 "항상 전체 task_id를 보여주고 절대 자르거나 축약하지 마세요"를 추가해요.
서브에이전트 런치가 실행 대신 대기열에 쌓임
문제: 서브에이전트 런치가 멈추거나 시작하는 데 오래 걸려요.
해결: 워커 풀이 고갈된 경우가 많아요. --n-jobs-per-worker로 풀 크기를 늘려요. 워커 풀 크기 잡기 섹션을 참고해요.
참고 구현 (Reference implementation)
async-deep-agents 저장소에는 LangSmith Deployments에 배포되는 Python과 TypeScript 예제가 모두 있어요. researcher와 coder 서브에이전트가 백그라운드 작업으로 실행되는 슈퍼바이저를 시연해요.
더 알아보기 (Learn more)
- 서브에이전트 (Subagents) — 동기 실행 서브에이전트 기초
- 딥 에이전트 개요 (Deep Agents overview)
- async-deep-agents 저장소