출력 손상을 수정하고 영속성을 구현했으며 TTL을 추가했습니다. 모두 v0.6에 포함되었습니다.

발행: (2026년 6월 12일 AM 02:25 GMT+9)
7 분 소요
원문: Dev.to

출처: Dev.to

신뢰할 수 없는 AI가 생성한 코드를 안전하게 실행하는 것은 명백히 어려운 문제입니다.

하지만 때때로 에이전트 워크플로우를 방해하는 문제들은 지루한 인프라 작업처럼 보이기도 합니다.

v0.6은 배관 작업으로 시작되었습니다:

  • 영구적인 샌드박스 레지스트리
  • TTL을 이용한 자동 정리

필요하지만 특별히 매력적인 작업은 아니었습니다.

그런데 테스트가 실패하기 시작했습니다.

출력 손상 문제

모든 실행이 다음과 같은 결과를 반환했습니다:

WARNING: Running pip as the 'root' user can result in broken permissions...
[notice] A new release of pip is available: 25.0.1 -> 26.1.2
hello world

실제 프로그램 출력은 의존성 설치 소음에 파묻혀 있었습니다.

터미널을 보는 사람에게는 짜증나는 일입니다.

하지만 실행 출력을 파싱하는 AI 에이전트에게는 완전히 깨진 상태입니다.

원인은 간단했습니다: 의존성 설치와 코드 실행이 하나의 Docker 호출에 연결돼 있었고, stderrstdout으로 리다이렉트되었습니다.

모든 것이 같은 스트림에 섞여 버린 것이죠.

해결책: Docker 호출을 두 번, 한 번이 아니라

우리는 작업을 분리했습니다.

호출 1: 의존성을 조용히 설치합니다.

subprocess.run(
    [...dependency_install_command],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
)

호출 2: 사용자 명령을 실행하고 출력을 캡처합니다.

result = subprocess.run(
    [...execution_command],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
)

return result.stdout

작은 변화이지만 원칙은 중요합니다:

AI 에이전트를 위해 인프라를 구축할 때, 깨끗한 출력은 API 계약의 일부입니다.

에이전트는 반환된 내용을 파싱합니다. 설치 로그, 경고 및 런타임 출력은 하나의 구분되지 않은 스트림으로 취급될 수 없습니다.

영속성: 메모리 내 딕셔너리 대신 SQLite

원래 샌드박스 레지스트리는 파이썬 딕셔너리였습니다.

서비스를 재시작하면 모든 샌드박스 레코드가 사라졌습니다.

컨테이너는 여전히 존재할 수 있지만, Jhansi는 더 이상 이를 알지 못했습니다. 서비스 재시작 후 재연결을 기대하는 어떤 에이전트 워크플로우도 실패하게 됩니다.

우리는 다음을 고려했습니다:

  • JSON: 간단하지만 부분 쓰기와 충돌 시 손상에 취약
  • Redis: 기본 TTL 지원 및 좋은 운영 모델 제공, 하지만 자체 호스팅을 위한 또 다른 서비스 필요
  • SQLite: 내구성 있고 트랜잭션을 지원하며 파이썬에 기본 포함

우리는 SQLite를 선택했습니다.

스키마는 의도적으로 작게 설계했습니다:

CREATE TABLE IF NOT EXISTS sandboxes (
    id TEXT PRIMARY KEY,
    language TEXT NOT NULL,
    container_id TEXT,
    workspace_path TEXT,
    status TEXT NOT NULL,
    created_at TEXT NOT NULL,
    expires_at TEXT NOT NULL
);

ORM도 없고, 마이그레이션 프레임워크도 없습니다.

그냥 SQLite가 잘하는 일을 그대로 수행하게 한 겁니다.

TTL: 생성 시점이 아니라 마지막 활성 시점 기준

각 샌드박스는 expires_at 값을 가지며, 초기값은 생성 후 1시간 뒤로 설정됩니다.

중요한 결정은 모든 실행이 시계를 리셋한다는 점입니다:

new_expires = (
    datetime.now(timezone.utc)
    + timedelta(seconds=TTL_SECONDS)
)

registry.update_expires_at(
    sandbox_id,
    new_expires,
)

백그라운드 작업이 60초마다 실행되어 만료된 샌드박스를 제거합니다.

이렇게 하면 TTL이 활동 기반이 되고, 시간 기반이 아닙니다.

에이전트가 20분 분석 동안 수십 번의 작은 실행을 수행할 수 있습니다. 생성 시점 기반 TTL은 활성 워크플로우 중에 샌드박스를 종료시킬 수 있지만, 마지막 활성 시점 기반 TTL은 그렇지 않습니다.

활성 샌드박스는 계속 사용할 수 있고, 유휴 샌드박스만 정리됩니다.

이것이 열어주는 가능성

영속성과 활동 기반 TTL 덕분에 Jhansi 샌드박스는 신뢰할 수 있는 실행 프리미티브가 되고 있습니다:

  • 샌드박스를 한 번 생성한다.
  • 필요할 때마다 반복해서 사용한다.
  • 서비스 재시작에도 살아남는다.
  • 활성 작업이 에이전트 아래에서 사라지지 않는다.

이것이 장기 실행 에이전트 워크플로우가 필요로 하는 기반입니다.

v0.7에서 기대할 수 있는 다음 기능: Server‑Sent Events를 통한 스트리밍 실행.

전체 명령이 끝날 때까지 출력을 기다릴 필요가 없습니다.

Jhansi는 AI가 생성한 코드를 안전하게 실행할 수 있는 오픈소스 클라우드 샌드박스입니다.

다음 명령으로 자체 호스팅할 수 있습니다:

docker compose up

AI 에이전트에게 필요한 것은 실행이며, 자격 증명이 아닙니다.

이 문제가 공감된다면 스타를 눌러 주세요: https://github.com/jhansi-io/petri

0 조회
Back to Blog

관련 글

더 보기 »