← 목록으로

실행은 격리하고, 세션은 이어간다: 망분리 환경의 Agent Platform 설계기

실행은 격리하고, 세션은 이어간다: 망분리 환경의 Agent Platform 설계기

요약

초기 Agent Platform의 대화 화면. 사용자가 Agent를 선택하고 업무를 요청합니다.
“지난달과 같은 기준으로 이번 달 데이터도 비교해줘.”

데이터를 분석하다 보면 자주 받는 요청입니다. 지난번에 쓴 쿼리를 찾아 기간을 바꾸고, 실행 결과를 비교해야 하죠. 빠진 데이터는 없는지, 집계 기준은 그대로인지도 확인해야 합니다. AI Platform 팀은 사람이 매번 반복하던 이 과정을 에이전트가 처리하도록 만들고 싶었습니다.

토스는 「전자금융감독규정」의 망분리 요건에 따라 금융 업무를 처리하는 내부망과 인터넷 업무 환경을 분리해 운영하고 있습니다. 자동화하려던 분석에 필요한 데이터도 내부망에 있었고, 외부로 옮길 수 없었습니다. 사람이 내부 도구로 하던 조회와 가공을 대신하려면, 에이전트가 내부망에서 코드를 실행하고 데이터를 분석할 수 있어야 했습니다.

그렇다고 에이전트에게 내부망의 모든 파일과 시스템을 열어줄 수는 없었습니다. 모델이 만든 코드가 작업과 관계없는 데이터를 읽거나 파일을 잘못 수정할 수도 있기 때문입니다. 프롬프트로 주의를 주는 것만으로는 이런 실수를 막기 어려웠습니다. 그래서 에이전트의 코드를 샌드박스 안에서 실행하고, 작업에 필요한 파일과 시스템에만 접근하도록 제한했습니다.

이전에 하던 작업을 이어가는 것도 중요했습니다. “지난달과 같은 기준으로 비교해줘”라는 요청을 처리하려면, 지난번에 사용한 코드와 집계 기준을 알아야 하니까요. 매번 새 환경에서 시작한다면 사용자는 코드를 다시 준비하고 분석 기준도 다시 설명해야 합니다. 사용자가 같은 내용을 매번 준비하지 않아도 되도록, 작업 파일과 대화 상태를 보관하기로 했습니다.

이 글에서는 이 두 가지 요구를 바탕으로 Agent Platform을 설계한 과정을 소개합니다.

  • 샌드박스에서 파일과 네트워크 접근을 어떻게 제한했는지
  • 실행 환경이 바뀌어도 작업을 계속할 수 있도록 파일과 대화 상태를 어떻게 관리했는지

모델이 만든 코드는 어디에서 실행할까

플랫폼 관리 기능은 Workspace 밖에 두고, 모델이 만든 코드와 명령은 Workspace 안의 제한된 환경에서 실행합니다.

*구조도에 나오는 GenAI Gateway와 MCP Gateway의 역할이 궁금하다면 이전 글을 참고해 주세요.

모델은 어떤 코드를 실행할지 결정할 수 있지만, 스스로 프로세스를 띄우거나 파일을 고치지는 않습니다. 모델의 응답에 따라 도구를 실행하고, 그 결과를 다시 모델에 전달하는 프로그램이 필요하죠. 이 과정을 반복하는 프로그램을 하네스(harness)라고 부릅니다. 분석 코드에 오류가 나면 그 내용을 모델에 전달하고, 수정한 코드를 다시 실행하는 것도 하네스가 맡습니다.

모델과 도구를 반복해서 호출하는 일은 기존 하네스에 맡겼습니다. 플랫폼은 요청자가 누구인지 확인하고, 작업에 필요한 실행 환경과 접근 권한을 준비합니다. 이렇게 역할을 나누면 하네스를 교체하더라도 사용자 관리나 저장소 연결을 다시 구현할 필요가 없습니다.

한 사용자가 여러 에이전트와 대화할 수 있으므로, 실행할 때는 누구의 작업 공간인지, 어떤 에이전트의 대화인지 구분해야 합니다.

  • Workspace: 사용자마다 제공하는 개인 작업 공간
  • Agent: 업무 지침과 사용할 도구를 설정한 에이전트
  • Session: 각 Agent와 나누는 개별 대화

예를 들어 한 Workspace 안에 데이터 분석 Agent와 문서 작성 Agent를 두고, 분석 Agent와 월별로 다른 대화를 나눌 수 있습니다.

Agent나 대화마다 실행 환경을 만들면 환경 준비와 저장소 연결도 반복해야 합니다. 이 반복을 줄이기 위해 사용자마다 Kubernetes의 실행 단위인 Pod를 만들고, Workspace의 실행 환경으로 재사용하기로 했습니다.

하나의 Pod를 함께 쓰더라도 Agent마다 접근할 수 있는 파일과 시스템은 제한해야 합니다. Agent의 지침과 도구 설정에 따라 실제 작업을 수행하는 것은 하네스입니다. 그래서 하네스를 격리하고, 그 안에서 실행한 도구와 코드에도 같은 접근 제한이 적용되도록 했습니다.

Workspace의 실행 환경은 오픈소스 OpenSandbox로 관리합니다. OpenSandbox가 Kubernetes에 Workspace별 Pod를 만들고 종료하는 일을 맡습니다. Runtime은 Pod 안에서 하네스를 바로 실행하지 않습니다. 먼저 bubblewrap으로 접근할 폴더와 통신 경로를 제한한 환경을 만들고, 그 안에서 하네스와 도구를 실행합니다.

사용자가 화면에서 분석을 요청하면 다음 순서로 실행을 준비합니다.

  1. Server가 사용자와 리소스 접근 권한을 확인
  2. Runner가 해당 Workspace의 실행 환경을 준비하고 Runtime에 실행 설정을 전달
  3. Runtime이 설정에 맞춰 하네스를 격리하고 실행

실행 준비가 끝나면 하네스가 모델과 도구를 호출하며 작업을 진행합니다. 사용자는 화면에서 응답과 실행 결과를 확인할 수 있습니다.

Server와 Runner는 Workspace Pod 밖에서 동작합니다. 하네스에는 업무에 필요한 입력과 도구를 제공하지만, 플랫폼 DB의 인증정보는 넘기지 않습니다. 분석 코드를 실행하는 데 다른 사용자의 설정이나 플랫폼 관리 기능에 접근할 권한까지 필요한 것은 아니니까요.

같은 Pod 안에서 Agent의 접근 범위를 나누기

Runtime은 Agent마다 허용한 폴더를 연결하고, 격리된 환경의 통신을 HTTP 프록시로 연결합니다. 같은 Workspace를 사용해도 각 Agent가 접근할 수 있는 파일은 다릅니다.

분석 Agent는 분석에 필요한 참고 문서를 읽을 수 있어야 합니다. 그렇다고 문서 작성 Agent의 작업 파일까지 마음대로 바꿀 수 있어서는 안 되겠죠. 화면에서 도구 목록을 숨기거나 파일 읽기 도구에서만 권한을 확인하는 것으로는 부족했습니다. 하네스가 셸 명령이나 직접 작성한 프로그램으로 같은 파일을 열 수도 있기 때문입니다.

그래서 도구의 종류와 관계없이, 코드를 실행하는 프로세스 자체의 접근 범위를 제한하기로 했습니다. Runtime은 bubblewrap을 사용해 하네스마다 별도의 Linux namespace를 구성합니다. namespace는 프로세스마다 파일시스템의 마운트 구성, 프로세스 목록, 네트워크 등을 격리할 수 있는 Linux 기능입니다.

Agent마다 별도 VM을 두면 게스트 커널까지 나눌 수 있습니다. 다만 VM별로 이미지와 작업 볼륨을 준비하고, 생성과 종료를 플랫폼의 실행 흐름에 연결해야 합니다. 저희가 필요로 한 것은 Workspace의 실행 환경을 재사용하면서, 그 안에서 하네스마다 파일과 통신 접근 범위를 다르게 설정하는 것이었습니다.

이를 위해 기존 Kubernetes와 OpenSandbox 구성 안에서 bubblewrap으로 하네스를 격리하는 방식을 선택했습니다. 실행 환경의 생성과 종료는 Workspace 단위로 관리하고, 하네스를 실행할 때 허용한 폴더와 통신 경로만 연결했습니다. Agent마다 별도의 게스트 운영체제를 준비하고 관리하지 않아도 필요한 접근 제한을 적용할 수 있었습니다. 다만 호스트 커널을 공유하므로 VM과 같은 커널 격리를 제공하지는 않습니다.

두 방식의 차이는 다음과 같습니다.

방식장점플랫폼에서 준비하고 관리할 것Agent마다 별도 VM 사용Agent마다 게스트 커널을 따로 사용하는 격리 환경을 구성할 수 있음VM 생성과 종료, 이미지 준비, 작업 볼륨 연결을 플랫폼 실행 흐름과 연동Workspace Pod 안에서 namespace 사용기존 Workspace 안에서 하네스마다 파일과 네트워크 접근 범위를 설정할 수 있음기존 Kubernetes/OpenSandbox 구성을 활용하면서, 하네스별 파일/네트워크/실행 권한을 설정하고 격리 동작을 검증

같은 Pod 안에서 실행하더라도, 각 하네스에 필요한 파일과 통신 경로만 제공해야 합니다. 이를 위해 Runtime은 하네스를 실행할 때 다음 세 가지를 제한합니다.

1️⃣ 접근할 수 있는 파일 실행에 필요한 기본 파일 외에는 Agent의 작업 폴더, 사용자가 추가로 허용한 폴더, 현재 세션의 상태가 저장된 경로만 마운트합니다. 마운트는 실제 디렉터리를 실행 환경 안의 경로에 연결하는 것입니다. 다른 Agent의 작업 폴더와 다른 세션의 상태 경로는 기본적으로 연결하지 않습니다. 참고 자료가 담긴 폴더처럼 수정할 필요가 없는 경로는 읽기 전용으로 연결합니다. 이렇게 하면 파일 쓰기 도구뿐 아니라 Python 코드나 셸 명령으로 수정을 시도해도 파일시스템에서 거부합니다. 새로운 도구를 추가하더라도 같은 격리 환경에서 실행한다면 이 제한을 그대로 적용받습니다.

2️⃣ 프로세스와 네트워크 PID namespace를 사용해 하네스에서 볼 수 있는 프로세스 목록을 격리합니다. 네트워크도 별도의 namespace로 구성해 Pod의 네트워크를 직접 사용할 수 없도록 합니다. 필요한 HTTP(S) 요청은 Runtime이 제공하는 프록시로 전달합니다. Runtime 프록시가 요청 목적지를 검사하는 방법과 OpenSandbox 인증 프록시가 실제 인증정보를 넣는 방법은 뒤에서 설명하겠습니다.

3️⃣ 실행 권한 하네스는 권한을 낮춘 사용자로 실행합니다. Linux가 프로세스에 부여하는 특수 권한인 capability도 제거해, 하네스가 격리 설정을 바꿀 수 없도록 합니다. 격리 환경을 준비하는 데 실패하면 실행을 중단합니다. 설정에 문제가 생겼을 때 격리 없이 실행한다면, 정해 둔 접근 제한을 지킬 수 없기 때문입니다.

접근 범위를 제한하는 것과 작업 결과를 검증하는 것은 다릅니다.

이 구조는 허용하지 않은 파일이나 시스템에 대한 접근을 제한합니다. 쓰기를 허용한 폴더 안에서 파일을 잘못 수정하거나 삭제하는 일까지 막아주지는 않습니다. 분석 결과가 정확한지도 별도로 확인해야 합니다.

같은 Agent의 대화들은 어떤 파일을 함께 쓸까

분석 Agent의 두 대화는 같은 작업 폴더를 사용합니다. 문서 Agent에는 다른 작업 폴더를 연결하고, 함께 필요한 참고 자료는 읽기 전용으로 연결할 수 있습니다.

서로 다른 Agent의 접근 범위를 나눈 뒤에는, 같은 Agent와 나누는 여러 대화가 무엇을 공유할지 정해야 했습니다. 대화마다 작업 폴더를 따로 만들면 파일 충돌을 줄일 수 있습니다. 하지만 지난 대화에서 만든 코드를 새 대화로 복사해야 하고, 어느 사본이 최신인지도 사용자가 관리해야 합니다.

월별로 대화는 나누되 분석 코드와 참고 자료는 계속 쓸 수 있도록, 파일과 대화 상태를 다르게 관리했습니다.

  • 작업 파일은 같은 Agent의 대화들이 함께 사용합니다. 지난달 분석에서 만든 compare.py를 새 대화에서도 읽고 실행할 수 있습니다. 한 대화에서 파일을 수정하면 다른 대화에서도 수정된 파일을 사용합니다.
  • 대화 상태는 Session마다 따로 보관합니다. 기존 대화를 이어가면 저장된 상태를 활용할 수 있습니다. 새 대화에는 이전 대화에서 정한 분석 조건이 자동으로 전달되지 않으므로, 필요한 조건이나 작업 요약을 알려 줘야 합니다.
  • 이전 대화를 열어도 파일이 당시 버전으로 돌아가지는 않습니다. 여러 대화에서 같은 파일을 동시에 수정하면 충돌할 수 있으므로, 공유 파일을 수정할 때는 주의가 필요합니다.

도구를 호출하되 인증정보는 넘기지 않기

분석에는 Workspace의 파일뿐 아니라 업무 시스템에서 조회한 데이터도 필요합니다. 하네스가 이런 데이터를 조회할 수 있도록 업무 도구에 연결하되, 필요한 서비스에만 접근하도록 통신 경로를 제한했습니다.

격리된 하네스의 HTTP(S) 요청은 전용 Unix 도메인 소켓을 통해 Runtime HTTP 프록시로 전달됩니다. Unix 도메인 소켓은 IP 네트워크를 거치지 않고 같은 호스트의 프로세스를 연결합니다. 덕분에 하네스에 Pod의 네트워크를 열어 주지 않고도 지정한 프록시로 요청을 보낼 수 있습니다.

하네스의 네트워크는 Pod와 격리되어 있습니다. 따라서 하네스가 프록시 환경변수를 무시하더라도 Pod의 네트워크를 이용해 직접 통신할 수는 없습니다.

이 글에서 다루는 내부망 구성에서는 통신을 두 단계로 제한합니다.

  • Runtime HTTP 프록시는 요청 목적지를 확인하고, Pod 자신의 주소처럼 하네스가 접근해서는 안 되는 대상을 차단
  • OpenSandbox의 egress 정책은 업무 서비스 등으로 나가는 통신에서 허용할 목적지를 정함

Runtime 프록시를 거친다고 모든 환경에 같은 통신 정책이 적용되는 것은 아닙니다. 접속을 허용할 업무 서비스는 환경마다 다르므로, OpenSandbox의 egress 정책도 그에 맞게 설정해야 합니다.

모델을 호출할 때는 GenAI Gateway를, 업무 도구를 호출할 때는 MCP(Model Context Protocol) Gateway를 거칩니다. MCP는 에이전트가 외부 도구의 기능을 확인하고 호출할 수 있도록 정한 인터페이스입니다. 하네스는 Gateway를 통해 샌드박스 밖의 업무 서비스에 필요한 기능을 요청합니다.

접속할 서비스를 제한하는 것과 별개로, 호출에 사용하는 인증정보도 보호해야 했습니다. 실제 API 키를 하네스의 환경변수에 넣으면 하네스가 실행한 코드에서도 그 값을 읽을 수 있습니다. 디버깅하려고 환경변수를 출력했다가 로그나 결과 파일에 키가 섞일 수도 있죠. 서비스를 호출할 때 키는 필요하지만, 하네스가 그 값을 직접 알 필요는 없었습니다.

이를 위해 인증정보를 대신 넣어 주는 OpenSandbox의 인증 프록시를 이용했습니다. 이 기능을 지원하는 HTTP(S) 연동의 호출 과정은 다음과 같습니다.

  1. 하네스가 요청 헤더에 실제 키 대신 대체값을 삽입
  2. Runtime HTTP 프록시가 목적지를 검사하고 요청을 중계
  3. OpenSandbox 인증 프록시가 미리 정한 목적지로 요청을 보낼 때, 지정된 헤더의 대체값을 실제 인증정보로 변경

이렇게 하면 하네스는 실제 키를 전달받지 않고도 해당 서비스를 호출할 수 있습니다.

다만 키를 숨기는 것과 사용 권한을 제한하는 것은 다릅니다. 하네스는 프록시를 통해 그 키에 허용된 기능을 그대로 사용할 수 있습니다. 따라서 Gateway는 모델과 도구의 사용 권한을, 업무 서비스는 해당 계정의 데이터 접근 권한을 확인해야 합니다. 문서 검색 서비스를 사용할 권한과 그 안의 개별 문서를 읽을 권한은 다르니까요.

에이전트가 읽은 데이터를 접속이 허용된 서비스로 보내는 일도 가능합니다. 이 구조만으로 데이터 반출을 모두 막을 수는 없습니다. 어떤 파일을 읽을 수 있는지, 어디에 연결할 수 있는지, 그곳에서 어떤 작업을 할 수 있는지를 함께 정해야 합니다.

인증정보 대체 방식의 적용 범위

이 방식은 OpenSandbox 인증 프록시가 지원하는 HTTP(S) 연동에 적용합니다. 모든 CLI나 개인 구독의 인증정보를 같은 방식으로 처리하는 것은 아닙니다.

Pod가 바뀌어도 분석을 이어가려면

작업 파일, 하네스가 저장한 세션 상태, 공통 실행 기록을 나누어 보관합니다. 세션 재개에는 하네스 상태를, 이력 조회와 요약 인계에는 공통 실행 기록을 사용합니다.

샌드박스의 실행 환경은 배포나 장애로 교체될 수 있습니다. 사용자가 이전 분석을 이어갈 수 있으려면, Pod가 없어져도 작업 파일과 대화 상태는 남아 있어야 합니다. 그래서 작업 파일과 세션 상태를 영속 볼륨(Persistent Volume, PV)에 저장하도록 했습니다. PV는 Pod와 별도로 유지되는 저장소입니다. 새 Pod를 만들면 이 볼륨을 다시 연결해 기존 파일과 상태를 사용합니다.

Runtime이 작업 폴더와 세션 상태 경로를 연결하면, 새로 시작한 하네스가 저장된 상태를 읽고 다음 요청을 처리합니다. 파일로 저장한 분석 결과는 다시 읽을 수 있지만, 종료된 프로세스의 메모리까지 복구되는 것은 아닙니다. 메모리에만 남아 있던 중간 계산값은 다시 계산해야 합니다.

그렇다면 대화 상태에는 무엇을 저장해야 할까요? 화면에 보이는 질문과 최종 답변만으로는 부족합니다. 예를 들어 모델이 조회 도구를 여러 번 호출했다면, 각각의 결과가 어떤 호출에서 나온 것인지도 알아야 다음 요청을 처리할 수 있습니다. 긴 대화를 압축한 내용처럼 하네스가 내부적으로 관리하는 정보도 필요합니다.

처음에는 플랫폼에 저장한 대화 기록을 하네스 형식으로 변환해 세션을 복원했습니다. 이렇게 하면 플랫폼의 기록만으로 상태를 다시 만들 수 있었지만, 변환 코드도 하네스의 저장 형식과 처리 규칙에 맞춰 관리해야 했습니다. 하네스가 도구 호출과 결과를 연결하는 방식을 바꾸면 변환 코드도 함께 고쳐야 했죠.

그래서 같은 하네스에서 대화를 이어갈 때는 그 하네스가 저장한 상태와 세션 ID를 다시 사용하도록 했습니다. 플랫폼은 상태가 저장된 경로를 기록해 두었다가 다음 실행에 연결합니다. 별도의 변환 코드로 내부 상태를 재구성하는 대신, 하네스가 자신의 상태를 직접 읽도록 한 것입니다.

저장된 상태를 찾지 못하면 오류를 반환합니다. 이전 상태를 읽지 못했는데도 대화를 이어간다면, 사용자는 앞서 정한 분석 조건이 반영됐다고 오해할 수 있기 때문입니다.

하네스가 저장한 상태를 그대로 사용하더라도 호환성 문제는 남습니다. 하네스 버전이 바뀌면 기존 상태를 읽을 수 있는지 확인해야 합니다. 다만 플랫폼이 하네스의 내부 형식을 직접 재현하고 관리하는 부담은 줄어듭니다.

여기서 이어간다는 것은 저장된 파일과 대화 상태를 바탕으로 다음 요청을 처리한다는 뜻입니다. 세션을 재개한다고 다음 동작까지 제공하는 것은 아닙니다.

  • 실패한 작업을 멈춘 지점부터 다시 실행하기
  • 수정한 파일을 이전 버전으로 되돌리기
  • 외부 서비스에서 이미 처리한 작업을 되돌리기

따라서 데이터 백업과 외부 작업의 중복 실행 방지는 별도로 다뤄야 합니다.

세션 재개와 이력 조회에 다른 기록을 사용하는 이유

대화를 이어갈 때는 하네스가 저장한 상태를 사용하지만, 지난 대화를 화면에 보여 줄 때는 이 상태 파일을 직접 읽지 않습니다. 하네스마다 저장 형식이 달라, 조회 기능도 각 형식의 변화에 맞춰 계속 수정해야 하기 때문입니다.

대신 하네스가 실행되는 동안 사용자 입력과 도구 호출, 실행 결과를 공통 형식으로도 남깁니다. 이 기록을 공통 실행 기록이라고 부릅니다. 메시지와 도구 호출 결과는 OpenResponses 형식으로 저장합니다. OpenResponses는 모델의 응답과 도구 호출을 일관된 구조로 표현하기 위한 공개 규격입니다.

하네스가 남긴 원본 이벤트도 함께 보관합니다. 지난 대화를 조회할 때는 저장된 기록을 읽으면 되므로 하네스를 다시 실행할 필요가 없습니다.

하네스 고유 상태와 공통 실행 기록의 용도를 정리하면 다음과 같습니다.

사용 목적사용하는 기록처리 방식같은 하네스에서 대화 이어가기하네스 고유 상태와 세션 ID하네스가 저장된 상태를 읽어 다음 요청을 처리화면에서 지난 대화 조회하기공통 실행 기록하네스를 실행하지 않고 메시지와 도구 실행 결과를 조회다른 하네스로 작업 넘기기공통 실행 기록과 허용된 작업 파일별도의 인계 요청에 따라 요약을 전달한 새 세션에서 시작

다른 하네스로 작업을 넘길 때는 기존 상태 파일을 그대로 사용할 수 없습니다. 그래서 인계를 요청하면 공통 실행 기록을 요약해 새 세션에 전달합니다. 이전 세션을 복원하는 것이 아니라, 요약을 참고해 새 대화를 시작하는 방식입니다.

예를 들어 지난 분석의 집계 조건과 결과 파일의 위치를 전달하면, 새 하네스는 이 설명과 접근이 허용된 작업 파일을 참고해 작업을 시작합니다. 요약에서 빠진 내용을 확인할 수 있도록 원문 기록도 보관하며, 기록을 읽지 못하면 요약을 만들지 않고 오류를 반환합니다.

기존 세션을 복원하지 못했다고 자동으로 이 방식으로 전환하지는 않습니다. 저장된 상태로 대화를 이어갈지, 요약을 전달해 새 대화를 시작할지는 요청할 때 구분합니다.

두 종류의 기록을 관리해야 하지만, 대화 조회와 요약 기능을 하네스마다 다른 저장 형식에 맞춰 구현하지 않아도 된다는 이점이 있습니다.

다음 분석도 같은 곳에서 이어갈 수 있도록

“지난달과 같은 기준으로 이번 달 데이터도 비교해줘.”라는 요청을 처리하려면, 지난번 코드와 결과 파일뿐 아니라 어떤 기준으로 분석했는지도 알아야 합니다. 이전 작업을 이어갈 수 있어야 하지만, 그 과정에서 필요한 범위보다 더 많은 파일과 시스템을 열어 줘서는 안 됩니다.

이를 위해 Agent Platform의 실행 환경과 기록을 다음과 같이 구성했습니다.

  • 접근 제한: 하네스가 실행하는 코드에도 파일과 네트워크 접근 제한을 적용
  • 작업 유지: Pod가 바뀌어도 같은 하네스에서 대화를 이어갈 수 있도록 작업 파일과 대화 상태를 보관
  • 공통 기록: 하네스에 관계없이 이력을 조회하고, 다른 하네스로 작업을 넘길 때는 요약을 전달한 새 세션에서 시작

이제는 사용자가 에이전트와 같은 작업 공간에서 직접 파일을 고치고, 팀원들과 에이전트를 함께 쓸 수 있도록 기능을 넓혀 가고 있습니다. 다음 편에서는 대화창 밖에서도 에이전트를 쓰기 위해 터미널과 팀의 업무 도구를 어떻게 연결했는지 소개하겠습니다.

← 목록으로