
샌드박스 내부에서 상태 유지형 리서치 에이전트(Stateful Research Agent)를 구축했습니다. 실제 수치는 다음과 같았습니다.
요약
무상태 실행 환경에서 발생하는 에이전트의 컨텍스트 유실 문제를 해결하기 위해 TensorLake를 활용한 상태 유지형(Stateful) 리서치 에이전트 구축 방법을 소개합니다. VM 스냅샷 기능을 통해 브라우저 세션과 프로세스 상태를 보존하며 작업을 재개하는 과정을 다룹니다.
핵심 포인트
- 무상태 환경의 컨텍스트 초기화 및 데이터 유실 문제 해결
- TensorLake의 suspend/resume 기능을 통한 VM 상태 보존
- 브라우저 봇 탐지 우회를 위한 ubuntu-vnc 이미지 활용
- 1초 미만의 빠른 세션 재개 및 지속성 확보
여러 페이지에 걸친 리서치 작업의 세 번째 단계에서, 에이전트는 모든 것을 잃었습니다.
충돌(Crash)이 발생한 것도, 예외(Exception)가 던져진 것도 아니었습니다.
함수는 반환되었고, 컨텍스트(Context)는 초기화되었으며, 방금 수집한 가격 데이터는 사라졌습니다.
이러한 실패는 예측 가능합니다: 무상태 실행 환경(Stateless execution environments)은 20분 동안 실행되는 브라우저 세션 전반에 걸쳐 상태(State)를 유지하도록 설계되지 않았습니다.
결국 언젠가는 마주하게 되며, 대개 최악의 순간에 발생합니다.
두 가지 표준적인 해결책(Workarounds)은 모두 번거롭습니다. 프롬프트(Prompt)에 상태를 밀어 넣는 방식은 토큰 비용(Token costs)이 문제가 되기 시작할 때까지는 작동합니다. 외부 상태 저장소(External state store)는 문제를 해결하지만, 이제 또 다른 서비스를 관리해야 합니다.
저는 짧은 수명의 코드 실행을 위해 E2B를 사용해 왔습니다. E2B는 이를 잘 처리하며, 초기 단계의 스냅샷(Snapshot) 지원을 포함하여 시간이 지남에 따라 지속성(Persistence) 기능을 추가해 왔습니다. 하지만 작업 중간에 일시 중지하고 다른 프로세스에서 재개해야 하는 에이전트의 경우, 상태 관리(State management)는 여전히 대부분 사용자의 몫입니다.
제 Discord의 누군가가 TensorLake를 언급했습니다. 저는 문서를 열어보고 이 구체적인 문제를 해결하기 위해 구축해 보기로 결정했습니다.
이 글에서는 샌드박스 내의 에이전트를 사용하여 데스크톱을 구축할 수 있는 단계들을 안내해 드리겠습니다.
설정부터 시작하겠습니다.
시각적 설명 우선
설정 (Setup)
제 관심을 가장 먼저 끌었던 것: 파일뿐만 아니라 실행 중인 프로세스와 열려 있는 브라우저 세션까지 전체 VM 상태를 보존하는 suspend() 및 resume() 기능이 있는 이름이 지정된 샌드박스(Named sandboxes)였습니다. 문서에 따르면 1초 미만의 재개(Resume)가 가능합니다.
0에서 실행까지 10분 소요:
pip install tensorlake
tl login # 또는 TENSORLAKE_API_KEY 환경 변수
무료 티어, 신용카드 불필요.
from tensorlake.sandbox import Sandbox
sandbox = Sandbox.create(
...
ubuntu-vnc 이미지는 VM(가상 머신) 내부에 실제 데스크톱과 Firefox를 제공하는 역할을 합니다. 실제 브라우저가 필요한 이유는 최신 가격 페이지들이 클라이언트 사이드 렌더링 (Client-side rendering)을 과도하게 사용하며, 헤드리스 스크래퍼 (Headless scraper)를 즉각 차단하는 봇 탐지 기술을 사용하기 때문입니다. 샌드박스 내부의 Firefox는 단순히 사람이 브라우징하는 것처럼 보입니다.
중요: ubuntu-vnc에는 Playwright가 사전 설치되어 있지 않습니다. 에이전트가 실행되기 전에 설치하십시오:
sandbox.run("pip", ["install", "playwright"])
sandbox.run("playwright", ["install", "chromium"])
첫 설정에는 2~3분이 소요됩니다. 그 이후에는 패키지가 일시 중단/재개 (Suspend/resume) 간에도 유지되므로, 비용은 한 번만 지불하면 됩니다.
지연 시간 (Latency): 실제 측정 결과
첫 번째 샌드박스는 Sandbox.create() 호출부터 running 상태가 될 때까지 대략 800-900ms가 소요되었습니다.
실제로 시간이 소요되는 부분은 다음과 같습니다:
샌드박스 생성: ~800ms (이름이 지정된 샌드박스, 최초 실행 시)
샌드박스 재개: ~400ms (일시 중단 상태로부터)
LLM 호출 (GPT-4o): 2,000-4,000ms (단계당, 모든 과정에서 가장 큰 비중 차지)
...
LLM 호출이 압도적인 비중을 차지합니다. 샌드박스의 오버헤드 (Overhead)는 병목 현상이 아닙니다. 주요 최적화 전략은 개별적인 왕복 (Round trip)을 교차로 수행하는 대신, 각 모델 호출 전에 브라우저 작업을 배치 (Batching) 처리하는 것입니다.
Tensorlake는 E2B 및 Modal보다 I/O가 1.6-1.9배 더 빠르다고 주장하는 SQLite 파일 시스템 벤치마크를 발표했습니다. 이는 자체 보고된 수치이며, 제가 독립적으로 검증할 수는 없었습니다. 제가 말할 수 있는 것은, 블록 기반 스토리지 (Block-based storage)가 빈번한 소규모 쓰기 작업에 대해 반응성이 좋게 느껴졌다는 점이며, 이는 리서치 에이전트가 매 단계마다 체크포인팅 (Checkpointing)을 할 때 사용하는 정확한 패턴입니다.
컴퓨터 사용 (Computer Use): 성공한 것과 실패한 것
데스크톱 API 자체는 깔끔합니다:
with sandbox.connect_desktop(password="tensorlake") as desktop:
png_bytes = desktop.screenshot()
desktop.move_mouse(640, 400)
...
PNG 바이트로 스크린샷을 찍고, 이를 디코딩하여 클릭할 위치를 파악한 뒤 좌표를 전송합니다. 각 브라우저 상호작용 (interaction)은 페이지 로드 속도에 따라 1~3초가 소요됩니다. API 호출에 비하면 느립니다. 하지만 서버 측에서는 단순히 Firefox를 사용하는 사람으로 보이기 때문에, 스크레이퍼 (scraper)를 차단하는 페이지에서도 작동합니다.
문제점: 좌표 방식은 레이아웃 (layout)이 고정되어 있다고 가정하지만, 레이아웃은 고정되어 있지 않습니다.
Weaviate의 가격 페이지에서 제 에이전트의 두 단계 사이에 A/B 테스트가 진행되었습니다. 토글 (toggle) 버튼이 30px 아래로 이동했습니다. 에이전트는 빈 공간을 클릭했습니다. 에러도, 예외 (exception)도 발생하지 않았습니다. 그저 아무 일도 일어나지 않았음을 보여주는 스크린샷뿐이었고, 오프셋 (offset)을 식별하기까지 20분 동안 디버깅을 해야 했습니다.
해결책: 좌표를 하드코딩 (hardcoding)하는 대신, GPT-4o Vision에 스크린샷을 전달하여 요소 (element)의 위치를 동적으로 파악합니다. 상호작용당 약 2초가 추가되지만, 레이아웃 드리프트 (layout drift)를 안정적으로 처리합니다. 신뢰성을 생각하면 그만한 가치가 있지만, 고빈도 작업 (high-frequency operations)을 수행하기에는 너무 느립니다.
DOM에 접근이 가능한 경우에는 샌드박스 (sandbox) 내부의 Playwright를 사용하는 것이 더 나은 경로입니다:
result = sandbox.run(
"python",
["-c", """
...
제가 최종적으로 결정한 하이브리드 전략은 다음과 같습니다:
| 상황 | 접근 방식 | 이유 |
|---|---|---|
| 봇 탐지 (bot detection)가 있는 사이트 | Vision + 좌표 | Playwright가 차단됨 |
| ... |
Vision을 첫 번째 도구가 아닌 폴백 (fallback)으로 사용하십시오. Vision은 레이아웃 변화를 처리합니다. Playwright는 속도를 처리합니다. 둘 중 어느 것도 두 가지를 모두 잘 수행하지는 못합니다.
상태 유지 (Statefulness): 실제로 중요했던 부분
세 단계(Pinecone 무료 티어 제한 확인, 월 $70 Starter 플랜 기록, Weaviate 문서 탐색 시작)를 거친 후, sandbox.suspend()를 호출했습니다.
샌드박스가 동결되었습니다. 파일 시스템 (filesystem), 메모리 (memory), 실행 중인 브라우저까지 모두 일시 중지되었습니다. 12분 후, 다른 터미널에서 다음과 같이 실행했습니다:
sandbox = Sandbox.connect("research-agent")
sandbox.resume()
약 400ms가 걸렸습니다. Weaviate 가격 탭은 여전히 열려 있었습니다. Tensorlake의 일시 중지/재개 (suspend/resume) 기능은 메모리와 실행 중인 프로세스를 포함한 전체 VM 상태를 보존합니다.
/workspace/research_notes.json에 기록된 모든 내용은 온전하게 유지되어 있었습니다.
제가 정착한 워크플로우(Workflow)는 다음과 같습니다: 의미 있는 단계마다 상태(State)를 명시적으로 기록한 후, 중단(Suspend)합니다.
# 각 단계가 끝난 후, 중단하기 전에:
sandbox.write_file(
"/workspace/state.json",
...
상태 파일은 연속성 메커니즘(Continuity mechanism) 역할을 합니다. 우아한 방식은 아니지만, 외부 데이터베이스의 필요성을 제거하며, 파일 시스템은 빠르고 중단(Suspend) 후에도 내구성이 유지되며, 다시 연결되는 어떤 프로세스에서도 읽을 수 있습니다.
확장 및 장애 처리 (Scaling and Failure Handling)
Sandbox.create()는 블로킹 동기 호출(Blocking synchronous call)입니다. 병렬 워크로드(Parallel workloads)를 위해서는 concurrent.futures로 감싸서 사용하세요:
from tensorlake.sandbox import Sandbox
from concurrent.futures import ThreadPoolExecutor
...
세 개의 동시 샌드박스(Sandboxes)를 지연 없이 실행했습니다. 20개나 50개 규모로는 테스트하지 않았습니다. 문서에는 초당 수백 개가 가능하다고 언급되어 있습니다. 부하 데이터(Load data)를 확보하기 전까지는 그 수치를 그대로 받아들이십시오.
참고: Tensorlake의 Python SDK v0.5.8은 I/O 바운드 오케스트레이션(I/O-bound orchestration)을 위해 스레딩(Threading)보다 더 깔끔한 대안을 제공하는 네이티브 비동기 API(Native async APIs)를 도입했습니다. v0.5.8 이상을 사용 중이라면, 동기 호출을 스레드 풀(Thread pool)로 감싸기 전에 이 API들을 먼저 고려해 볼 가치가 있습니다.
첫날부터 구축할 가치가 있는 패턴들:
멱등적 상태 기록 (Idempotent state writes). 의미 있는 단계마다 상태를 기록하십시오. 에이전트가 실행 도중 실패하더라도, 다음 호출 시 파일을 읽어 완료된 작업을 건너뛸 수 있습니다. 이는 자동으로 이루어지지 않습니다.
위험한 작업 전 체크포인트 생성 (Checkpoint before risky operations). sandbox.checkpoint()는 복구 가능한 스냅샷(Snapshot)을 생성합니다. 기본적으로 스냅샷은 파일 시스템 상태를 보존합니다. 전체 메모리 상태를 보존하는 것은 명시적인 옵션으로 지원됩니다. 어떤 방식이든, 작업이 잘못될 경우 새로운 샌드박스로 복구할 수 있습니다:
# 파일 시스템 스냅샷 (기본값)
snapshot = sandbox.checkpoint()
...
이름이 지정된 샌드박스 (Named sandboxes). 오케스트레이션 프로세스가 종료되더라도, 다른 프로세스가 Sandbox.connect("sandbox-name")를 통해 다시 연결하여 마지막으로 기록된 상태부터 재개할 수 있습니다.
아키텍처 경계 (Architectural boundary): Tensorlake는 에이전트를 위한 실행 환경 (execution environment) 및 런타임 (runtime)을 제공합니다. 즉, VM, 파일 시스템 (filesystem), 프로세스 생명주기 (process lifecycle), 네트워킹 (networking)을 제공합니다. 이는 에이전트 프레임워크 (agent framework)가 아닙니다. 재시도 로직 (Retry logic), 서킷 브레이커 (circuit breakers), LLM 속도 제한 백오프 (LLM rate-limit backoff) 등은 그 상위의 오케스트레이션 계층 (orchestration layer)에 속해야 합니다. 예를 들어 LangChain, LlamaIndex, 커스텀 하네스 (custom harness), 또는 에이전트를 구동하기 위해 사용 중인 무엇이든 해당됩니다. 이러한 분리는 의도적인 것이며, 기능적 공백이 아닙니다.
멘탈 모델 (The Mental Model)
제 설계 방식에 대한 생각을 변화시킨 부분은 다음과 같습니다:
┌─────────────────────────────────────────────┐
│ Your Agent │
│ (LLM + tool calling logic) │
...
샌드박스 (sandbox)는 에이전트가 아닙니다. 에이전트가 작동하는 안정적인 환경입니다. 에이전트가 재개될 때, 환경은 에이전트가 떠났던 바로 그 상태 그대로 유지됩니다. 에이전트의 로직은 외부에 존재하며, 초기화되지 않은 세계에 다시 연결됩니다.
이는 여러분이 구축할 수 있는 것들을 변화시킵니다. 실행 환경 (execution environment)이 오케스트레이션 세션 (orchestration session)보다 더 오래 지속될 때, 한 시간 동안 실행되며 15개의 페이지를 탐색하고 구조화된 보고서를 작성하는 에이전트가 실현 가능해집니다. 순수하게 휘발성인 실행 (ephemeral execution) 환경에서는 불가능합니다.
비교 (How It Compares)
vs E2B:
vs E2B:
- 두 서비스 모두 Firecracker 마이크로VM (microVMs)을 사용합니다. E2B는 200ms 미만의 콜드 스타트 (cold starts)를 마케팅 포인트로 내세우지만, 커뮤니티 보고에 따르면 실제 p50 수치는 400-600ms에 더 가깝습니다. Tensorlake의 샌드박스 (sandbox) 생성은 제 테스트 결과 약 800ms였습니다.
- E2B는 최근 릴리스에서 스냅샷 (snapshot) 및 일시 중지/재개 (pause-resume) 기능을 추가했습니다. 상태 유지 (statefulness) 격차는 1년 전보다 좁혀졌습니다. Tensorlake의 일시 중지/재개 (suspend/resume)는 실행 중인 프로세스, 브라우저 세션 등을 포함한 전체 실행 VM 상태를 1초 미만의 시간 내에 보존합니다. E2B의 메모리 스냅샷 (memory snapshot) 지원은 여전히 초기 단계로 설명됩니다.
- Tensorlake는 자체 벤치마크에서 파일 시스템 I/O (filesystem I/O)가 1.6-1.9배 더 빠르다고 주장합니다. 이는 자체 보고된 수치입니다. 독립적인 참조를 위해 말씀드리자면, Tensorlake는 최근 ComputeSDK 샌드박스 벤치마크의 세 가지 카테고리 모두에서 상위 2위 안에 들었습니다.
- 두 서비스 모두 SDK 레이어에서 DOM 레벨의 요소 선택 (DOM-level element selection) 기능을 제공하지 않습니다.
vs Modal:
- Modal은 Firecracker 대신 gVisor를 사용하며, 상태가 없는 (stateless) 함수 실행을 중심으로 설계되었습니다. 상태 유지형 (stateful) 장기 실행 에이전트 (long-running agents)도 작동은 하지만 더 많은 설정이 필요합니다. 문서에 따르면 콜드 스타트 (cold starts)는 약 1-1.5초입니다.
vs Stagehand (BrowserBase):
- Stagehand는
locator()를 통해 DOM 레벨의 선택자 (CSS, XPath, 자연어)를 제공합니다. 순수 브라우저 자동화 (browser automation) 측면에서는 이는 실제적인 사용성 (ergonomic)의 이점입니다. - Tensorlake는 완전한 VM을 제공합니다. 코드 실행, 파일 관리, 패키지 설치, 브라우저 사용을 동일한 환경에서 수행할 수 있습니다. 만약 이러한 조합이 필요하다면, 완전한 VM 모델은 좌표 복잡성 (coordinate complexity)을 감수할 가치가 있습니다.
- 브라우저 자동화만 필요하다면? Stagehand가 더 집중된 도구입니다.
from tensorlake.sandbox import SandboxClient
client = SandboxClient()
...
구축 결과물
세션이 끝날 무렵, 에이전트는 다음과 같은 비교 보고서를 생성했습니다: 7개 페이지에 걸쳐 추출된 Pinecone 대 Weaviate의 가격 비교이며, 두 번의 일시 중지 (suspensions)와 오케스트레이션 머신 (orchestrating machine)의 전체 재시작 과정 중에도 노트가 보존되었습니다.
report_bytes = sandbox.read_file("/workspace/comparison_report.md")
print(bytes(report_bytes).decode("utf-8"))
정확합니다. 티어 (tier) 이름과 숫자가 모두 올바릅니다.
Tensorlake은 어려운 부분들, 즉 검색 로직 (retrieval logic), 상태 스키마 (state schema), 하이브리드 브라우저 전략 (hybrid browser strategy)을 해결하지 않았습니다. 대신 이러한 요소들이 구축되는 동안 방해하지 않고 자리를 지켰습니다. 인프라 마찰 (infrastructure friction)의 대부분은 상태 관리 (state management)에서 발생했으며, 샌드박스 파일 시스템 (sandbox filesystem)이 상태 저장소 (state store)가 되면서 그 문제의 대부분이 사라졌습니다.
시작하기 전에 알아야 할 세 가지
속도는 샌드박스의 문제가 아니라 시스템의 문제입니다. LLM 호출이 단계별 지연 시간 (latency)의 대부분을 차지합니다. 샌드박스 시작 시간을 쫓기보다는, 각 모델 호출 전에 브라우저 작업들을 배치 (batching) 처리함으로써 최적화하십시오.
첫날부터 중단 (interruption)을 고려하여 설계하십시오. 의미 있는 단계가 끝날 때마다 상태 (state)를 기록하십시오. 이는 샌드박스가 충돌할 것이기 때문이 아니라, 예상치 못한 중단 이후 다른 프로세스로부터 재개하는 것이 예외적인 상황이 아닌 실제 시나리오이기 때문입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
