본문으로 건너뛰기

© 2026 Molayo

LangChain헤드라인2026. 05. 21. 03:46

프로덕션 환경의 Deep Agents를 뒷받침하는 런타임 (Runtime)

요약

프로덕션 환경에서 장기 실행되는 Deep Agents를 안정적으로 운영하기 위해서는 단순한 프롬프트와 도구를 넘어 내구성이 있는 실행(Durable execution)을 지원하는 런타임 인프라가 필수적입니다. 본문은 체크포인트 실행, 메모리 관리, 인간 참여형(HITL), 관찰 가능성 등 에이전트의 안정성을 보장하는 핵심 런타임 기능과 LangSmith Deployment(LSD)의 역할을 설명합니다.

핵심 포인트

  • 프로덕션 에이전트에는 내구성이 있는 실행(Durable execution)을 위한 체크포인트 실행 기술이 필수적임
  • 런타임은 메모리, 멀티테넌시, 인간 참여형(HITL), 관찰 가능성을 포함하는 인프라 계층을 의미함
  • Deep Agents는 모델 불가지론적(Model-agnostic)이며 MCP, A2A와 같은 개방형 프로토콜을 지향함
  • LangSmith Deployment(LSD)와 Agent Server가 프로덕션 환경의 에이전트 실행을 담당함

핵심 요약 (Key Takeaways)

  • 훌륭한 하네스 (Harness)는 에이전트에게 적절한 프롬프트 (Prompts), 도구 (Tools), 기술 (Skills)을 제공합니다. 하지만 프로덕션 환경에서 장시간 실행되는 에이전트를 배포하려면 내구성이 있는 실행 (Durable execution), 메모리 (Memory), 멀티테넌시 (Multi-tenancy), 인간 참여형 (Human-in-the-loop), 그리고 관찰 가능성 (Observability)이 필요합니다. 이러한 인프라는 하네스 아래에 존재하며, 충돌 (Crashes), 배포 (Deploys), 그리고 장시간 실행되는 작업 (Long-running tasks) 전반에 걸쳐 에이전트가 안정적으로 실행되도록 유지합니다.
  • 내구성이 있는 실행 (Durable execution)은 다른 모든 요소가 의존하는 기초입니다. 몇 분 또는 몇 시간 동안 실행되거나, 인간의 승인을 위해 일시 중지되거나, 실행 중간의 배포 상황에서도 살아남는 에이전트는 모두 프로세스 경계(Process boundaries)를 넘어 중단, 재개 및 재시도가 가능한 체크포인트 실행 (Checkpointed execution)이 필요합니다. 스트리밍 (Streaming), 인간 참여형 (Human-in-the-loop), 크론 잡 (Cron jobs), 그리고 동시 메시지 처리 (Concurrent message handling)는 모두 이를 기반으로 구축됩니다.
  • 프로덕션 에이전트는 개방적이고 모델 불가지론적 (Model-agnostic)인 인프라가 필요합니다. Deep Agents는 MIT 라이선스를 따르며, 에이전트는 개방형 프로토콜 (MCP, A2A)을 통해 노출되고, 메모리는 사용자의 PostgreSQL에 저장됩니다. 팀은 에이전트가 어떻게 작동하는지에 대한 완전한 가시성을 유지하며, 재작성 없이 에이전트를 변경할 수 있는 능력을 갖게 됩니다.

프로덕션 환경에 장기적 지평 (Long horizon) 에이전트를 배포하려면 목적에 맞게 설계된 인프라가 필요합니다. 이 가이드는 내구성이 있는 실행 (Durable execution), 메모리 (Memory), HITL (Human-in-the-loop), 관찰 가능성 (Observability), 그리고 Deep Agents가 이 모든 것을 어떻게 프로덕션으로 배포하는지 다룹니다.

좋은 에이전트를 구축하려면 좋은 하네스 (Harness)가 필요합니다. 그 에이전트를 배포하려면 좋은 런타임 (Runtime)이 필요합니다.

하네스는 에이전트가 해당 도메인에서 성공할 수 있도록 모델 주변에 구축하는 시스템입니다. 여기에는 프롬프트 (Prompts), 도구 (Tools), 기술 (Skills), 그리고 에이전트를 정의하는 모델 및 도구 호출 루프 (Model and tool calling loop)를 지원하는 모든 것이 포함됩니다. 런타임은 그 아래에 있는 모든 것입니다: 내구성이 있는 실행 (Durable execution), 메모리 (Memory), 멀티테넌시 (Multi-tenancy), 관찰 가능성 (Observability), 그리고 팀이 매번 새로 만들 필요 없이 에이전트가 프로덕션에서 계속 실행되도록 유지하는 기계 장치입니다.

이 가이드는 에이전트를 배포할 때 나타나는 프로덕션 요구 사항, 이를 충족하는 런타임 기능, 그리고 deepagents deploy가 이러한 기능들을 배포 가능한 형태로 어떻게 패키징하는지에 대해 설명합니다.

프로덕션 에이전트를 위한 런타임 기능 (Runtime capabilities)

이 섹션 전체에서 "런타임 (the runtime)"은 LangSmith Deployment (LSD)와 그 Agent Server를 의미합니다. LSD는 프로덕션 환경에서 에이전트 (agents)를 실행하며, Agent Server는 어시스턴트 (assistants), 스레드 (threads), 실행 (runs), 메모리 (memory), 그리고 예약된 작업 (scheduled jobs)을 위한 인터페이스 역할을 합니다. 아래 표는 각 프로덕션 요구사항을 충족하는 런타임 프리미티브 (runtime primitive)를 매핑하여 보여줍니다.

내구성 있는 실행 (Durable execution)

에이전트는 루프 (loop)를 실행함으로써 작동합니다. 프롬프트 (prompt)가 주어지면, 모델은 추론하고, 도구 (tools)를 호출하며, 결과를 관찰하고, 작업이 완료되었다고 판단할 때까지 이 과정을 반복합니다.

밀리초 단위로 반환되는 일반적인 웹 요청 (web request)과 달리, 이 루프는 몇 분 또는 몇 시간 동안 지속될 수 있습니다. 단일 실행 (single run) 내에서 수십 번의 모델 호출을 수행하거나, 하위 에이전트 (subagents)를 생성하거나, 초안을 승인할 사람을 무기한 기다릴 수도 있습니다. 해당 루프의 어느 지점에서든 발생하는 충돌 (crash), 배포 (deploy), 또는 일시적인 장애 (transient failure)가 그전까지 수행한 작업 내용을 삭제해서는 안 됩니다.

실제로 이는 두 가지 측면에서 체감됩니다.

긴 실행 시간은 인프라 장애 (infrastructure failures)로부터 살아남아야 합니다. 소스를 수집하고 조사 내용을 합성하는 데 20분을 소비하는 리서치 에이전트 (research agent)의 경우, 워커 프로세스 (worker process)가 종료되었다고 해서 처음부터 다시 시작할 수는 없습니다. 에이전트는 이미 토큰 (tokens) 비용을 지불했고 도구 호출 (tool calls)을 실행했기 때문입니다. 여러분이 원하는 것은 이전의 모든 상태 (state)를 온전하게 유지한 채, 마지막으로 완료된 단계부터 재개 (resumption)하는 것입니다.

에이전트는 멈추고 기다릴 수 있어야 합니다. 트랜잭션 (transaction) 승인을 위해 사람의 확인을 기다리며 일시 중지하는 에이전트는, 사람이 30초 후에 응답할지 혹은 3일 후에 응답할지 알 수 없습니다. 그 전체 시간 동안 워커 프로세스나 클라이언트 연결 (client connection)을 점유하는 것은 실행 불가능합니다. 에이전트는 진정으로 멈춰야 합니다. 즉, 리소스를 해제하고 워커를 반환한 뒤, 나중에 정확히 멈췄던 지점부터 다시 시작해야 합니다.

이 두 가지 요구사항은 동일한 방식으로 해결됩니다: 바로 내구성 있는 실행 (durable execution)입니다.

  • 에이전트는 자동 체크포인팅 (automatic checkpointing) 기능이 있는 관리형 작업 큐 (managed task queue)에서 실행되므로, 어떤 실행이든 중단된 정확한 시점부터 재시도 (retried), 재생 (replayed), 또는 재개 (resumed)될 수 있습니다.

  • 그래프 실행 (graph execution)의 각 슈퍼 스텝 (super-step)은 thread_id를 키로 하여 지속성 계층 (persistence layer, 기본적으로 PostgreSQL)에 체크포인트를 기록합니다.

  • 실행(run)에 대한 지속적인 커서 (persistent cursor) 역할을 합니다. 워커 (worker)가 충돌하면, 실행에 대한 임대 (lease)가 해제되고 다른 워커가 최신 체크포인트 (checkpoint)로부터 이를 이어받습니다.

  • 에이전트 (agent)가 인간의 입력을 기다릴 때, 프로세스는 자신의 슬롯 (slot)을 양도하며 실행은 재개될 때까지 무기한 대기 (sleep) 상태가 됩니다.

  • 설정 가능한 재시도 정책 (retry policies)을 통해 백오프 (backoff), 최대 시도 횟수 (max attempts), 그리고 노드 (node)별로 어떤 예외 (exception)가 재시도를 트리거할지를 제어할 수 있습니다.

내구성 (Durability)은 이 목록의 나머지 요소들이 의존하는 토대입니다. 실행이 프로세스 경계를 넘어 일시 중단 및 재개될 수 있기 때문에, 에이전트는 인간의 입력을 무기한 기다릴 수 있고, 백그라운드에서 실행될 수 있으며, 실행 도중 배포 (deploy)가 이루어져도 생존할 수 있고, 상태 (state)를 손상시키지 않으면서 동시 입력 (concurrent inputs)을 처리할 수 있습니다.

메모리 (Memory)

에이전트에게는 두 가지 서로 다른 종류의 메모리가 필요하며, 이 둘의 구분은 매우 중요합니다.

**단기 메모리 (Short-term memory)**는 에이전트가 단일 대화 내부에서 축적하는 것입니다. 교환된 메시지, 수행된 도구 호출 (tool calls), 실행 과정에서 구축된 중간 상태 (intermediate state) 등이 이에 해당합니다. 이는 thread_id에 범위가 지정되어 해당 스레드의 체크포인트 (checkpoint)에 저장되며, 대화가 종료되면 (개념적으로) 사라집니다. 동일한 스레드에서의 후속 메시지는 해당 스레드에서 이전에 발생한 모든 내용을 볼 수 있습니다.

**장기 메모리 (Long-term memory)**는 에이전트가 대화 전반에 걸쳐 유지하는 것입니다. 여기에는 여러 대화에 걸쳐 학습된 사용자 선호도, 프로젝트 관례 및 모범 사례 (best practices), 또는 새로운 쿼리 (query)가 추가될 때마다 강화되는 지식 베이스 (knowledge base) 등이 포함될 수 있습니다. 이 중 어느 것도 단일 스레드에 속하지 않습니다. 이는 에이전트가 수행하는 모든 대화에 걸쳐 지속되어야 하는 사용자 수준 또는 조직 수준의 컨텍스트 (context)입니다. 체크포인트 (checkpoint) 상태는 단일 스레드에 범위가 지정되어 있기 때문에, 체크포인트만으로는 이를 수행할 수 없습니다.

장기 메모리는 에이전트 서버 (Agent Server)의 내장 스토어 (built-in store)를 위한 것입니다. 이는 메모리가 네임스페이스 튜플 (namespace tuples, 예: (user_id, "memories"))에 의해 정리되는 키-값 (key-value) 인터페이스입니다.

) 및 스레드(threads) 간에 지속됩니다. 에이전트는 한 대화에서 스토어(store)에 기록하고, 다음 대화에서 이를 읽어옵니다. 기본적으로 PostgreSQL을 기반으로 하며, 임베딩(embedding) 설정을 통한 시맨틱 검색(semantic search)을 지원하므로 에이전트가 정확한 일치(exact match)가 아닌 의미에 따라 메모리를 검색할 수 있습니다. 또한 다른 저장 특성이 필요한 경우 커스텀 백엔드(custom backend)로 교체할 수 있습니다. 네임스페이스(namespace) 구조는 유연합니다. 사용자, 어시스턴트(assistant), 조직(organization) 또는 데이터 모델에 적합한 임의의 조합으로 범위를 지정할 수 있습니다.

수개월 동안 축적된 메모리는 시스템이 생성하는 가장 가치 있는 데이터 중 일부이기 때문에, 그것이 어디에 저장되는지가 중요합니다. 스토어는 API를 통해 직접 쿼리(query)할 수 있으며, 셀프 호스팅(self-host)하는 경우 사용자의 자체 PostgreSQL 인스턴스에 저장됩니다. 이 데이터를 사용자가 제어할 수 있는 표준 형식으로 유지하는 덕분에 모델 간의 마이그레이션(migration), 데이터 분석, 또는 에이전트 외부에서 이를 기반으로 한 구축이 가능해집니다.

멀티테넌시 (Multi-tenancy)

에이전트가 한 명 이상의 사용자를 서비스하는 순간, 싱글 플레이어(single-player) 모드에서는 존재하지 않았던 일련의 문제들이 나타납니다. 이는 세 가지 별개의 관심사로 나뉘며, 에이전트 서버(Agent Server)는 각각을 고유한 프리미티브(primitive)로 처리합니다.

한 사용자의 데이터를 다른 사용자의 데이터로부터 격리하는 것. 사용자 A의 실행(run)은 사용자 A의 스레드만 건드려야 하며, 사용자 A의 메모리만 읽어야 합니다. 커스텀 인증(authentication)은 모든 요청에서 미들웨어(middleware)로 실행됩니다. @auth.authenticate 핸들러는 들어오는 자격 증명(credential)을 검증하고 사용자의 신원과 권한을 반환하며, 이는 실행 컨텍스트(run context)에 부착됩니다. 이후 @auth.on.threads, @auth.on.assistants.create 등에 등록된 인가(authorization) 핸들러는 생성 시 리소스에 소유권 메타데이터(ownership metadata)를 태깅하고 읽기 시 필터 딕셔너리(filter dictionaries)를 반환함으로써 누가 무엇을 보고 수정할 수 있는지 강제합니다. 핸들러는 가장 구체적인 것부터 가장 덜 구체적인 순서로 매칭되므로, 하나의 전역(global) 핸들러로 시작하여 모델이 성장함에 따라 리소스별 핸들러를 추가할 수 있습니다.

사용자를 대신하여 에이전트가 행동하도록 허용하기. 에이전트는 종종 사용자의 자격 증명 (credentials)을 사용하여 제3자 서비스(third-party services)를 호출해야 합니다. 예를 들어, 사용자의 캘린더를 읽거나, 사용자의 Slack에 게시물을 올리거나, 사용자의 리포지토리 (repo)에 PR을 여는 작업 등이 있습니다. Agent Auth는 이러한 패턴을 위한 OAuth 절차 (OAuth dance)와 토큰 저장 (token storage)을 처리하므로, 개발자가 직접 리프레시 흐름 (refresh flow)을 관리하지 않아도 에이전트가 런타임 (runtime)에 사용자 범위의 자격 증명을 얻을 수 있습니다. 사용자는 한 번만 인증하면 되며, 에이전트는 이후의 실행 과정에서 사용자를 대신하여 행동할 수 있습니다.

시스템 자체를 운영할 수 있는 권한 제어하기. 최종 사용자 (end-user)의 액세스와는 별개로, 귀하의 팀원 중 누가 에이전트를 배포하고, 구성하며, 트레이스 (traces)를 확인하거나 인증 정책 (auth policies)을 변경할 수 있는지에 대한 문제가 있습니다. RBAC (역할 기반 액세스 제어, Role-Based Access Control)는 이러한 운영자 수준의 액세스 제어를 처리합니다.

이 세 가지 계층은 다음과 같이 구성됩니다: 최종 사용자는 귀하의 인증 핸들러 (auth handler)를 통해 인증하고, 에이전트는 Agent Auth를 통해 제3자 서비스를 호출하며, 귀하의 팀은 RBAC 정책에 따라 배포를 운영합니다.

Human-in-the-loop (HITL)

에이전트는 루프 (loop)를 실행함으로써 작동합니다: 프롬프트 (prompt)가 주어지면, 모델이 추론하여 도구 (tools)를 호출하기로 결정하고, 결과를 관찰하며, 당면한 작업을 완료했다고 판단할 때까지 이 과정을 반복합니다. 대부분의 경우 귀하는 이 루프가 중단 없이 실행되기를 원할 것입니다. 거기에서 가치가 창출되기 때문입니다. 하지만 때로는 핵심적인 결정 지점에서 루프 중간에 사람이 개입해야 할 때가 있습니다.

이러한 상황이 발생하는 두 가지 일반적인 경우가 있습니다:

  • 제안된 도구 호출(tool call) 검토. 에이전트가 중대한 작업(이메일 전송, 금융 거래 실행, 파일 삭제 등)을 수행하기 전에, 사용자가 에이전트가 무엇을 하려는지 정확히 확인하고 어떻게 대응할지 결정하기를 원할 수 있습니다. 이메일 사례를 들어보겠습니다. 에이전트가 메시지 초안을 작성한 후 전송하기 전에 일시 중지합니다. 사용자는 이를 그대로 승인하거나, 발송 전에 제목이나 본문을 수정할 수 있으며, 혹은 이유와 구체적인 수정 요청을 포함하여 거절함으로써 에이전트가 내용을 수정하고 다시 시도하도록 할 수 있습니다.

에이전트의 명확한 질문 요청. 때때로 에이전트는 도구가 부족해서가 아니라, 정답이 인간의 판단이나 선호도에 달려 있기 때문에 스스로 해결할 수 없는 결정 지점에 도달합니다. 추측하는 대신, 에이전트는 다음과 같이 질문을 직접 드러낼 수 있습니다: "해당 패턴과 일치하는 설정 파일(config files)을 세 개 찾았습니다. 어떤 것을 수정해야 할까요?" 또는 "이것을 스테이징(staging) 환경에 배포해야 할까요, 아니면 프로덕션(production) 환경에 배포해야 할까요?" 사용자의 답변은 인터럽트(interrupt)의 반환 값(return value)이 되며, 에이전트는 정확히 멈췄던 지점부터 계속 진행합니다.

Agent Server는 두 가지 프리미티브(primitives)를 통해 이를 처리합니다: 실행을 일시 중지하고 호출자에게 페이로드(payload)를 전달하는 interrupt()와, 사용자의 응답과 함께 실행을 재개하는 Command(resume=...)입니다. 이 둘을 결합하면 승인 게이트(approval gates), 초안 검토 루프(draft review loops), 입력 검증(input validation), 그리고 실행 중간에 인간의 개입이 필요한 모든 워크플로(workflow)를 구축할 수 있습니다.

내부적으로 interrupt()는 런타임의 체크포인터(checkpointer)를 트리거하여 전체 그래프 상태(graph state)를 영구 저장소(durable storage)에 기록하며, 이때 영구적인 커서(cursor) 역할을 하는 thread_id를 키(key)로 사용합니다. 그 후 프로세스는 리소스를 해제하고 무기한 대기합니다. 특정 노드 전후에서 멈추는 정적 중단점(static breakpoints)과 달리, interrupt()는 동적(dynamic)입니다. 코드 어디에나 배치할 수 있고, 조건문으로 감싸거나, 도구 함수(tool function) 내부에 삽입하여 승인 로직이 도구와 함께 움직이도록 할 수 있습니다. 몇 분, 몇 시간, 혹은 며칠 후에 Command(resume=...)가 도착하면, resume 값은 interrupt() 호출의 반환 값이 되며, 실행은 정확히 멈췄던 지점에서 다시 시작됩니다. resume

어떠한 JSON 직렬화 가능한 (JSON-serializable) 값이라도 수용할 수 있으므로, 응답은 승인(approve) 또는 거절(reject)에 국한되지 않습니다. 검토자(reviewer)는 수정된 초안을 반환할 수 있고, 사람이 누락된 컨텍스트를 제공할 수 있으며, 다운스트림 시스템(downstream system)이 계산된 결과를 주입할 수 있습니다. 병렬 브랜치(parallel branches)가 각각 interrupt()를 호출하면, 모든 대기 중인 인터럽트(interrupts)가 함께 드러나며, 단일 호출로 한꺼번에 재개하거나 응답이 돌아오는 대로 하나씩 재개할 수 있습니다.

실시간 상호작용 (Real-time interaction)

Human-in-the-loop는 실행이 사람의 검토나 입력을 위해 일시 중지될 수 있는 상호작용 모드입니다. 이는 때로는 즉시, 때로는 훨씬 나중에 이루어집니다. 이와 별개로, 사용자가 있는 동안 에이전트가 활발하게 작동할 때 발생하는 "라이브 세션 (live session)" 문제들이 있습니다. 즉, 진행 상황을 가시화하는 것(스트리밍, streaming)과 동시 메시지를 조정하는 것(double-texting)입니다.

스트리밍 (Streaming)

응답을 생성하는 데 30초가 걸리는 에이전트는 사용자가 진행 중인지, 멈췄는지, 아니면 곧 실패할 것인지에 대한 아무런 신호 없이 스피너(spinner)만 바라보게 만듭니다. 또한 전체 작업이 완료될 때까지 답변을 읽기 시작할 수도 없습니다. 스트리밍은 이 두 가지 문제를 모두 해결합니다. 에이전트가 출력을 생성함에 따라 부분적인 출력이 클라이언트로 흐르므로, 사용자는 응답이 실시간으로 구체화되는 것을 볼 수 있습니다.

스트리밍 API는 원하는 세밀도(granularity)에 따라 여러 모드를 지원합니다: 각 그래프 단계(graph step) 이후의 전체 상태 스냅샷(full state snapshots), 상태 업데이트(state updates)만 제공, 토큰 단위(token-by-token)의 LLM 출력, 또는 사용자 정의 애플리케이션 이벤트(custom application events) 등이 있습니다. 이들을 조합할 수도 있습니다. 스트리밍 실행(client.runs.stream())은 단일 실행(single run) 범위로 제한되며, 스레드 스트리밍(client.threads.joinStream()

AI 자동 생성 콘텐츠

본 콘텐츠는 LangChain Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0