핵심 요약
- 에이전트가 더 큰 목표를 맡게 되면서, 여러 장기 작업을 동시에 진행하는 동안 사용자에게 응답성을 유지해야 합니다.
- Async subagents는 에이전트를 오케스트레이터(orchestrator)와 서브에이전트(subagents)로 분리합니다. 오케스트레이터는 사용자에게 계속 사용 가능한 상태를 유지하고, 서브에이전트는 백그라운드에서 작업을 수행한 후 완료 시 오케스트레이터에게 보고합니다.
- 배경 작업은 이제 에이전트 인프라의 표준이 되고 있으며, Claude Code와 같은 코딩 에이전트부터 OpenAI의 dots와 같은 개인화된 에이전트에 이르기까지 적용되고 있습니다.
당신이 큰 프로젝트를 맡길 때마다 사라졌다가 완료되었을 때만 돌아오는 비서실장(chief of staff)을 상상해 보세요. 아마 즉시 해고할 것입니다. 훌륭한 비서실장은 사용자에게 계속 접근 가능하면서도 배경에서 프로젝트를 진행하고 그 과정을 업데이트해 줍니다. 에이전트에게 더 큰 목표를 향해 작업하도록 요청함에 따라, 이들은 동일하게 두 가지 속성을 갖추어야 합니다. 즉, 이전의 모든 맥락을 바탕으로 새로운 지침에 즉시 반응하는 '응답성(responsive)'과 여러 개의 대규모 작업을 동시에 진행하는 '생산성(productive)'입니다.
우리가 오랫동안 에이전트를 구축하는 데 사용해 온 아키텍처로는 이 두 가지 요구 사항을 모두 충족할 수 없습니다. 표준 ReAct 루프에서는 각 장기 도구 호출(long tool call)이 루프를 차단하고, 다른 모든 작업은 그 뒤로 대기열에 쌓입니다. 대신, 우리는 에이전트가 사용자에게 남아있는 하나의 주체와 장기 작업을 수행하는 백그라운드 워커(background workers) 사이로 분리되는 생태계의 수렴을 목격하고 있습니다. Claude Code는 기본적으로 서브에이전트를 백그라운드에서 실행하며, OpenAI의 dots는 여러 가지 작업을 동시에 진행하기 위해 작업을 백그라운드 워커에게 전달하고, DeepSeek Harness는 사람들과 통신하는 라우터(router)를 중심으로 구축된 슈퍼 에이전트와 병렬로 작업을 수행하는 백그라운드 워커를 활용합니다.
저희는 이 패턴을 비동기 서브에이전트를 갖춘 Deep Agents에 도입하고 있습니다. 오케스트레이터가 사용자에게 계속 사용 가능한 상태를 유지하며 대화의 맥락을 정리하는 동안, 비동기 서브에이전트가 백그라운드 워커로 실행되어 완료 시 보고합니다. 이로 인해 에이전트가 맡을 수 있는 작업량은 하나의 루프 길이보다는 백그라운드 워커의 개수에 따라 증가하게 됩니다.
비동기 서브 에이전트 작동 방식
에이전트는 루프(loop) 안에서 실행됩니다. 모델은 도구를 호출하고 결과를 기다린 다음 다음에 무엇을 할지 결정합니다. 역사적으로, 도구와 서브 에이전트는 결과가 반환될 때까지 이 루프를 블로킹(blocking)했습니다. 마치 느린 호출들을 순차적으로 대기하는 코드를 실행하는 것과 같았습니다. 비동기 서브 에이전트는 asyncio.create_task나 아직 기다리지 않는(await) Promise와 같은 작업처럼 작동합니다. 이 호출은 즉시 반환되고, 작업은 백그라운드에서 독립적으로 실행되며, 결과는 나중에 돌아옵니다.
Sync vs async subagents: the orchestrator hands work to async subagents and stays free to talk to the user
비동기 작업은 시스템의 두 가지 속성에 의존합니다:
사용자가 오케스트레이터에 접근할 수 있다. 오케스트레이터는 사용자가 직접 상호 작용하는 에이전트입니다. 서브 에이전트는 더 이상 그(오케스트레이터)의 루프를 점유하지 않기 때문에, 사용자는 모든 서브 에이전트가 완료되기를 기다리는 대신, 자신의 루프가 바쁘지 않을 때 언제든지 오케스트레이터에 접근할 수 있습니다.
작업은 블로킹 없이 병렬로 실행된다. 오케스트레이터는 전체 배치(batch)가 반환될 때까지 기다리는 대신, 작업이 들어올 때마다 서브 에이전트를 시작합니다.
오케스트레이터는 다음 다섯 가지 도구를 사용하여 비동기 서브 에이전트를 관리합니다:
start_async_task: 서브 에이전트를 자체 스레드에서 시작하고 즉시 반환합니다.
check_async_task: 작업의 상태와, 완료되면 그 결과를 가져옵니다.
update_async_task: 실행 중인 작업에 새로운 지침을 보냅니다.
cancel_async_task: 실행 중인 작업을 중지합니다.
list_async_tasks: 모든 작업과 그 상태를 나열합니다.
실행 중인 서브 에이전트를 제어(Steering)하는 것은 오케스트레이터에 접근하는 것과는 별개입니다. 오케스트레이터가 update_async_task로 후속 조치를 보낼 때, 해당 서브 에이전트 정의의 update_strategy 필드가 어떤 일이 일어날지 결정합니다:
"interrupt"(기본값): 현재 실행을 중단하고 새로운 지침으로 서브 에이전트를 재시작합니다. "Postgres 대신 사용하라."와 같은 수정 사항에 사용하세요.
"enqueue": 후속 조치를 현재 실행이 끝날 때까지 보류합니다. "완료되면 테스트를 실행하라."와 같은 추가 작업에 사용하세요.
알림 작동 방식
모든 미완료된 작업은 오케스트레이터의 시스템 프롬프트에 나열되므로, 항상 무엇이 진행 중인지 알고 있습니다. 이는 check_async_task를 사용하여 폴링(poll)할 수 있습니다.
또는 작업 자체가 결과를 보고할 수 있습니다:
- 오케스트레이터(orchestrator)가 바쁜(busy) 상태라면, 각 모델 호출 전에 미완료된 작업을 확인하고 실행 도중에 결과를 가져옵니다. - 유휴(idle) 상태라면, 서브 에이전트(subagent)가 새로운 실행으로 이를 깨웁니다. - 승인 대기(paused) 상태라면, 승인은 그대로 유지되며 사용자가 답변을 하는 즉시 결과가 도착합니다.
An async subagent reports back whether the orchestrator is busy, idle, or paused on an approval
승인이 필요한 서브 에이전트는 '대기 중(waiting)'으로 표시되며, 사용자는 오케스트레이터를 방해하지 않고 서브 에이전트 자체 스레드에서 답변을 합니다. Slack의 Managed Deep Agents에서는 요청이 원래 스레드로 다시 게시됩니다.
우리는 이 패턴이 서브 에이전트를 넘어 모든 도구(tool)에 일반화될 것으로 예상합니다. OpenAI는 최근 비동기 도구 호출(async tool calling) 지원을 추가했는데, 이는 모델이 도구를 호출한 후에도 계속 작동하고 사용자의 애플리케이션이 나중에 요청으로 결과를 다시 보내는 방식입니다.
비동기 서브 에이전트 사용하기 (Use async subagents)
비동기 서브 에이전트는 Deep Agents for Python 및 JavaScript와 Managed Deep Agents에서 사용할 수 있으며, 서브 에이전트를 비동기로 만드는 것은 하나의 필드만 추가하면 됩니다:
from managed_deepagents import define_deep_agent
agent = define_deep_agent(
name="research-assistant",
...
Managed Deep Agents에서는 모든 서비스된 에이전트가 '범용(general-purpose)' 서브 에이전트의 비동기 복사본도 얻게 되어, 런타임에 위임된 작업을 포그라운드(foreground)에서 실행할지 백그라운드(background)에서 실행할지 결정할 수 있습니다.
작업 알림(Task notifications) 기능은 베타 버전이므로 API가 변경될 수 있습니다. async-deep-agents 레포지토리에는 Python 및 TypeScript로 작성된 연구원(researcher)과 코더(coder) 서브 에이전트를 갖춘 작동하는 슈퍼바이저가 있습니다.
비동기 서브 에이전트를 사용해 보고, GitHub 이슈나 포럼에 의견을 알려주세요.