
오늘날 대부분의 에이전트 프레임워크는 데스크톱을 가정합니다. 한 사용자, 한 대의 머신, 하나의 프로세스. 에이전트는 노트북이 켜져 있는 동안 실행되며, 로컬 파일시스템에 쓰고, 환경 변수에 API 키를 보관하다가, 터미널이 닫히면 종료됩니다. 문제가 생기면 사용자가 재시도합니다. 에이전트가 패키지를 필요로 하면, pip install 명령어가 사용자의 Python 환경에 설치합니다. 상태, 비밀 정보, 라이프사이클 모두 하나의 신뢰된 경계 안에 있습니다.
클라우드 에이전트 인프라는 그런 사치가 하나도 없습니다.
에이전트는 매번 새로 부팅되는 샌드박스에서 실행되며, 낯선 사람과 공유하는 하드웨어 위에서, 사용자가 만나지 못하는 호출자(스케줄, HTTP 요청, 다른 에이전트)에 의해 트리거됩니다. 사용자는 보통 실행이 발생할 때 자고 있습니다. 샌드박스 내부의 코드는 적대적일 수 있습니다. 파일시스템은 배포를 견뎌내야 합니다. 자격 증명은 에이전트가 있는 곳에 존재할 수 없습니다. 데스크톱이 무료로 제공하는 모든 보장(지속성, 신원, 네트워크 신뢰, 재시도)은 명시적인 시스템으로 재구축되어야 합니다.
우리는 지난 몇 달 동안 CREAO 에서 그 레이어를 강화하는 데 집중했습니다. 두 가지 교훈을 얻었습니다. 데스크톱 에이전트를 출시해 본 적이 있고, 그것이 클라우드로 이동할 때 무엇이 변하는지 궁금했다면, 이것이 바로 그 변화입니다.
교훈 1: 느리게 변하는 것과 빠르게 변하는 것을 분리하세요

데스크톱에서는 사용자 환경과 에이전트의 런타임이 동일한 것입니다. 같은 주기로, 같은 사람에 의해 업데이트됩니다. 그러나 클라우드에서는 그렇지 않습니다.
에이전트 앱은 플랫폼 측에 상태를 축적합니다. 주식 분석가가 matplotlib을 설치하고, 시장 데이터를 다운로드하고, 차트 스크립트를 작성합니다. 그 환경은 에이전트의 근육 기억입니다. 사용자가 만족할 때 그 순간의 샌드박스 스냅샷을 고정시키고, 사용자가 다시 환경을 편집할 때까지 그 스냅샷을 얼려둡니다. 모든 실행은 동일한 이미지로 부팅됩니다. 동일한 패키지, 동일한 파일, 동일한 버전. 월요일의 실행이 금요일과 동일하게 작동합니다. 그 밑에 있는 것이 전혀 움직이지 않았기 때문입니다.
이것이 데스크톱 프레임워크가 공짜로 제공할 수 없는 속성입니다. 6개월 전의 pip install 명령어는 오늘날 다른 버전으로 해석됩니다. 클라우드 스냅샷은 영원히 동일한 바이트로 해석됩니다. 재현성은 플랫폼이 사용자에게 제공해야 하는 의무이며, 얼려진 스냅샷은 이를 제공하는 가장 저렴한 방법입니다.
그러면 결합 문제가 나타납니다.
사용자 환경을 고정시키는 동일한 이미지에는 러너 코드(각 실행에서 에이전트를 관리하는 저희가 개발한 작은 하네스 라이브러리)도 포함되어 있습니다. 사용자는 자신의 환경이 그대로 유지되기를 원합니다. 저희는 러너를 하루에도 여러 번 배포하고 싶습니다. 하나의 아티팩트, 두 가지 상반된 요구 사항.
첫 번째 수정은 무식했습니다. 부팅 시 스냅샷 내부의 러너가 방금 배포한 버전과 일치하는지 확인합니다. 일치하지 않으면 스냅샷을 버리고 깨끗한 템플릿으로 부팅합니다. 이 방식은 작동했고 아무도 불평하지 않았습니다. 피해는 배포 후 첫 번째 실행에만 영향을 미쳤습니다.
무인 실행이 그 보호막을 무너뜨렸습니다. 월요일 오전 9시의 cron 작업은 8시 55분에 배포를 했다고 해서 환경을 잃어서는 안 됩니다. 우리가 은밀히 위반하고 있던 계약은 "환경을 변경할 때까지 고정된다"는 것이었습니다.
수정 방법을 알아내는 데 예상보다 오래 걸렸습니다. 사용자 환경과 러너 코드는 완전히 다른 속도로 변합니다. 사용자는 자신이 선택한 때에 에이전트를 편집합니다. 저희는 플랫폼을 하루에도 여러 번 배포합니다. 이를 하나의 아티팩트로 취급하면 모든 배포에서 선택을 강요받았습니다: 오래된 러너 코드를 유지하거나, 사용자가 명시적으로 보존해 달라고 요청한 고정된 환경을 파괴하거나.
우리가 도달한 모델은 운영 체제가 업데이트를 처리하는 방식에서 차용했습니다. 커널은 변합니다. 홈 디렉토리는 변하지 않습니다. 보안 패치를 설치하기 위해 디스크를 지우지 않습니다.
우리는 같은 경계를 그었습니다. 샌드박스는 사용자의 고정된 스냅샷에서 그대로 부팅됩니다. 그런 다음 러너만 핫스왑합니다. 시퀀스는 다음과 같습니다:
- 새 러너를 샌드박스 내의 임시 디렉토리에 스테이징합니다.
- node --check 로 유효성을 검사하여 문법 오류가 있으면 라이브에 반영하기 전에 잡아냅니다.
- 원자적으로 교체합니다: 이전 러너의 불변 플래그를 해제하고, 새 파일을 복사한 후 chattr +i 로 다시 잠그고, chattr 바이너리 자체를 숨겨서 샌드박스 코드가 잠금을 되돌릴 수 없도록 합니다.
- V8 컴파일 캐시(/home/user/.cache/v8-compile-cache/*)를 제거하여 새 파일이 실제로 로드되고 오래된 바이트코드를 실행하지 않도록 합니다.
- 어떤 단계라도 실패하면 샌드박스를 종료하고 새 것으로 재시도합니다. 절반만 업그레이드된 상태로 에이전트가 실행되는 일은 없습니다.
전체 교체는 약 300 밀리초가 걸립니다. 성공적인 실행 후 러너 코드가 교체된 경우에만 다시 스냅샷을 찍어 업데이트된 코드를 사용자 이미지에 반영하므로 다음 실행에서는 교체를 건너뜁니다. 플랫폼 배포는 사용자 상태를 폐기하지 않습니다. 새 러너를 그 안에 접어 넣습니다. 사용자의 패키지, 파일, 커스터마이제이션은 변경 없이 그대로 유지됩니다.
이 교훈에서 한 가지를 얻는다면, 진단 질문을 기억하세요. 클라우드 플랫폼에서 유지하는 모든 것에 대해 묻습니다: 이 아티팩트의 변경 주기는 누가 통제하는가? 사용자와 플랫폼이 모두 소유한다면 결국 결합에 대한 대가를 치르게 됩니다. 소유권 경계를 따라 아티팩트를 분할하고 각 측이 자신의 시계에 따라 업데이트하도록 하세요.
교훈 2: 비밀 정보를 실행 경계 밖에 두세요

이 교훈이 클라우드 에이전트 인프라를 다른 모든 것과 구분 짓는 부분입니다.
데스크톱 에이전트는 사용자로서 실행됩니다. 사용자의 키를, 사용자의 머신에서, 사용자의 네트워크에 대해 사용합니다. 클라우드 에이전트는 아무도 아닌 존재로서, 공유 하드웨어에서, 공개 인터넷을 상대로, 잠재적으로 적대적인 프롬프트에서 LLM이 작성한 코드를 실행합니다. 보안 모델은 샌드박스 내부의 코드가 이미 손상되었다고 가정해야 하며, 그 반대를 바라서는 안 됩니다.
저희가 지키는 규칙은 간단합니다. 장기 자격 증명은 절대 샌드박스 내부에 존재하지 않습니다.
에이전트가 인증된 서비스(Slack, GitHub, 사용자 자신의 API)를 호출해야 할 때, 토큰을 보관하지 않습니다. 샌드박스 외부에서 실행되는 API 브릿지에 로컬 HTTP 요청을 보냅니다. 브릿지가 호스트 측에서 OAuth 토큰을 첨부하고 호출을 전달합니다. 응답이 돌아올 때 토큰은 샌드박스의 메모리나 환경에 들어오지 않습니다.
흥미로운 부분은 브릿지가 샌드박스가 요청할 권한이 있는지 어떻게 아는가입니다. 두 가지 검사가 의도적으로 계층화되어 있습니다.
첫째, IP 허용 목록. 브릿지는 샌드박스 호스트가 위치한 내부 네트워크 범위에서만 연결을 수락합니다. 다른 곳(개발자 노트북, 유출된 URL, 공개 인터넷)에서의 호출은 애플리케이션 코드가 실행되기 전에 네트워크 계층에서 차단됩니다. 이는 브릿지를 하나의 물리적 인프라에 고정시키고 외부의 누구에게도 무용지물로 만듭니다.
둘째, 실행마다 발급되는 단기 JWT입니다. 샌드박스가 부팅될 때 플랫폼이 해당 특정 실행에 범위가 지정된 토큰(어떤 사용자, 어떤 앱, 어떤 세션, 실행 기간을 포함하고 그 이상은 아닌 만료 시간)에 서명합니다. 샌드박스는 모든 브릿지 호출 시 이를 제시합니다. 브릿지는 서명을 확인하고 만료 시간을 체크한 후에야 사용자의 저장된 자격 증명을 해결하여 서버 측에서 첨부합니다. 샌드박스가 탈취되면 공격자는 실행과 함께 소멸하고 해당 세션으로 범위가 지정된 호출만 승인하는 토큰을 얻게 됩니다. 탈취할 마스터 자격 증명은 없습니다.
동일한 브릿지가 결제 차감, 로그, 메트릭을 외부로 전달하므로, 샌드박스 경계를 양방향으로 교차하는 유일한 인터페이스입니다. 샌드박스 내부의 다른 모든 것은 기본적으로 손상된 것으로 간주됩니다.
프롬프트 인젝션으로 에이전트가 process.env를 웹훅으로 덤프하도록 속이더라도, 공격자는 내부 네트워크에서만 작동하고 실행과 함께 만료되는 단기 JWT만 얻을 수 있습니다. 이 속성이 우리로 하여금 신뢰할 수 없는 사용자 코드를 공유 인프라에서 실행하면서도 잠을 설칠 일이 없게 해줍니다.
밑에 깔린 패턴
안정적이고 안전한 클라우드 에이전트 인프라는 새로운 시스템이 아닙니다. 예외 없이 유지되는 몇 가지 속성입니다:
- 상태는 샌드박스에 있으며, 사용자가 변경할 때까지 고정됩니다.
- 코드는 상태와 독립적으로 핫스왑 가능합니다.
- 자격 증명은 호스트 측에 있으며, 절대 에이전트 내부에 있지 않습니다.
- 하나의 실행 파이프라인이 모든 호출자(사람, 스케줄러, 다른 소프트웨어)에 서비스합니다.
마지막 속성이 전체 설계의 핵심입니다. 하나의 executeAgent 함수가 UI 클릭, 예약된 실행, API 호출을 모두 처리합니다. 결제 시스템, 크레딧 차감 로그, 관찰 가능성 신호 — 모두 사람이 Run 을 클릭했는지, cron 이 실행되었는지, 스크립트가 API 를 호출했는지에 관계없이 동일합니다. 새로운 트리거 표면을 추가하는 것은 라우팅 변경일 뿐, 아키텍처 변경이 아닙니다. 에이전트 자체는 누가 방아쇠를 당겼는지 알지도 못하고 신경도 쓰지 않습니다.
이것이 데스크톱 프레임워크가 제공할 수 없고, 클라우드 버전을 구축할 가치가 있게 만드는 것입니다. 노트북의 에이전트는 노트북에 묶여 있습니다. 클라우드의 에이전트는 나머지 스택이 호출할 수 있는 함수입니다. 사용자는 한 번 작성합니다. 플랫폼은 배포를 견디고, 공유 하드웨어에서 안전하게 실행되며, 사용자가 예상하지 못한 호출자를 수용하도록 만듭니다.
에이전트는 자연어 인터페이스를 가진 함수입니다. 구현은 사용자에게 속합니다. 트리거 표면, 런타임, 보안 경계는 플랫폼에 속합니다. 규율은 각 계층이 자신의 시계에 따라 진화하도록 구축하고, 다른 사람이 찾기 전에 시스템 간의 균열을 발견하는 데 시간을 쓰는 것입니다.
그것이 다음 표면을 저렴하게, 그리고 안전하게 출시할 수 있게 만드는 방법입니다.





