다음에 올 것을 만들어가는 기술에 대한 심층 기사.

Talorys가 서버리스에서 상태 유지형 AI 에이전트를 돌리는 방법

서버리스에서 상태를 가진 AI 에이전트를 어떻게 돌릴까? Talorys는 Cloudflare Durable Objects와 SQLite로 무료 티어에서 구현합니다.

작은 집 하나가 서버 블록으로 가득한 끝없는 격자 속의 빛나는 큐브 하나에 묶여 있는 일러스트.
에이전트 하나, 객체 하나, 계정 하나: 애플리케이션 전체가 남의 격자 위 단일 Durable Object 안에 들어 있습니다.

여기서 제 파격적인 주장을 하나 펼쳐 보겠습니다. 개인용 AI 에이전트는 책상 위의 라즈베리 파이보다 서버리스 인프라에 더 잘 맞습니다. Talorys 프로젝트는 npx create-talorys@latest 명령 한 줄로 여러분의 Cloudflare 계정에 배포되는 오픈소스 개인 비서인데, 상태를 가진 AI 에이전트를 상태 없는 서버리스 위에서 돌리는 문제가 풀 수 있는 수준을 넘어 자연스럽게 맞아떨어진다는 가장 확실한 증거입니다. 그리고 이것이 "셀프 호스팅"에 해당하느냐를 두고 Hacker News에서 벌어지는 논쟁은 아예 엉뚱한 질문을 붙잡고 있는 겁니다.

저는 수년간 인프라를 운영하고 뒤처리도 해 왔습니다. 셀프 호스팅 개인 소프트웨어를 죽이는 건 배포가 아니라 3개월 차입니다. 로그로 디스크가 차고, TLS 인증서가 만료되고, 스케줄링을 맡은 systemd 유닛이 어떤 건지 기억이 안 나는 순간이죠. Talorys는 서버 자체를 두지 않음으로써 이런 실패 유형을 통째로 피해 갑니다. 채팅, 메모리, 작업, 예약 알림 등 필요한 모든 것이 Cloudflare 무료 티어에서 돌아갑니다. 프런트엔드는 Pages, API는 비공개 Worker, 모든 상태는 SQLite 기반 Durable Object, 모델은 Workers AI가 담당합니다. 사용자 한 명 기준 월 비용은 0원입니다.

진짜 문제: 에이전트는 상태를 가지고, 서버리스는 그렇지 않다

AI 에이전트는 겉으로는 요청-응답 애플리케이션처럼 보이지만 실제로는 오래 살아남는 상태 덩어리입니다. 대화 기록, 지속되는 메모리, 작업 목록, 아무도 로그인하지 않은 상태에서도 아침 7시에 실행되는 예약 작업, 여러 단계로 이어지는 체인이 도는 동안 도구 출력값을 담아 둘 공간이 필요하죠. 이 중 어느 것도 전형적인 서버리스 모델과 맞지 않습니다. 그 모델에서 함수는 금붕어와 같아서, 깨어나 요청 하나를 처리하고 모든 걸 잊어버리니까요.

업계의 표준 답은 상태를 가장자리에서 다시 조립하는 것입니다. 세션 데이터는 Redis, 영속 레코드는 Postgres, 크론은 큐와 스케줄러, 의미 기반 검색은 벡터 데이터베이스로 처리하죠. 작동은 하지만, 관리형 서비스들을 엮어 서버 팜을 다시 만든 셈이고 비용과 운영 부담도 그만큼 따라옵니다. 저는 팀이 에이전트 로직을 짜는 시간보다 에이전트 하나를 위해 상태 있는 서비스 다섯 개를 연결하는 데 더 많은 엔지니어링 시간을 쓰는 걸 여러 번 봤습니다. 전에도 주장했듯이 LLM 에이전트는 결국 분산 시스템이고, 분산 시스템은 좋은 의도가 죽으러 가는 곳이죠.

Talorys는 반대로 갑니다. 모든 상태를 정확히 한 곳에 둡니다. personal-agent라는 이름의 Durable Object 하나가 대화, 메모리, 작업, 노트, 프로젝트, 자동화, 세션, 설정, 사용량 추적까지 전체 세계를 내장 SQLite 데이터베이스에 담습니다. KV도, D1도, R2도, Vectorize도 없습니다. README에도 이것들은 프로비저닝하지 않는다고 명시되어 있죠. 우연이 아니라 의도된 아키텍처입니다.

Durable Object는 치트키다

Durable Object는 지금 클라우드 컴퓨팅에서 가장 조용하지만 가장 급진적인 기본 요소라고 생각하며, 이건 과장이 아닙니다. Durable Object는 단일 인스턴스 코드이며, 물리적으로 함께 위치한 강한 일관성의 트랜잭션 스토리지를 가집니다. personal-agent 객체로 요청이 들어오면 Cloudflare가 콜드 상태에서도 밀리초 안에 그 객체를 깨우고, 로컬 SQLite를 대상으로 코드를 실행한 뒤, 유휴 상태가 되면 다시 얼려 둡니다. 오래 도는 서버 프로세스의 편리함을 누리면서도 과금 방식은 Lambda처럼 쓸 수 있는 셈입니다.

단일 사용자 개인 에이전트에서는 이 모델의 악명 높은 제약들이 오히려 장점이 됩니다:

  • 단일 쓰기 주체가 곧 기능입니다. 사용자가 한 명이면 쓰는 주체도 한 명입니다. 다중 테넌트 상태에서 생기는 동시성 문제가 사라지고, 모든 데이터에 직렬화된 트랜잭션 접근이 공짜로 따라옵니다.
  • co-location이 지연 시간을 없앱니다. 에이전트의 메모리 조회, 작업 검색, 대화 기록 접근은 다른 가용 영역의 데이터베이스로 가는 네트워크 왕복이 아니라 로컬 디스크의 SQLite 읽기입니다.
  • 알람이 cron을 대체합니다. Durable Object 알람으로 객체가 스스로 깨어날 시점을 예약할 수 있습니다. 리마인더, 반복 루틴, 일일 요약은 어떤 머신도 켜져 있지 않아도 실행됩니다. 객체는 잠들고, 알람이 깨우고, 일을 하고, 다시 잠듭니다.
  • 대기 시간은 무료입니다. 유휴 시간에는 비용이 들지 않습니다. 하루 23시간 놀고 있는 개인 비서야말로 서버리스 과금 모델이 만들어진 바로 그런 워크로드입니다.

제가 이 프로젝트에서 더 많은 사람이 얻어 가길 바라는 통찰이 바로 이것입니다. 애플리케이션이 본질적으로 단일 테넌트라면, 즉 개인 도구, 고객별 워크스페이스, 기기별 코디네이터라면 "테넌트당 Durable Object 하나" 패턴은 예전에는 VPS가 필요했던 것을 줍니다. 개념적으로 항상 실행 중인 작은 컴퓨터인데, 유휴 상태에서는 비용이 없고 시스템 관리자가 필요 없습니다. 스케줄링 이야기만으로도 입장료는 충분히 값어치를 합니다. 개인용 cron 서버를 운영해 본 사람이라면 실패 패턴을 알 겁니다. 서버가 재부팅되었는데 cron 데몬이 돌아오지 않고, 2주 뒤에야 리마인더가 조용히 멈춰 있었다는 걸 알게 되죠. 알람은 인프라가 책임지는 스케줄링이라서, 챙겨야 할 것 하나가 줄어듭니다.

네트워크 모델은 대부분의 실서비스 구성보다 낫다

보안 배경이 있는 제 눈에 가장 번쩍 들었던 부분이 여기입니다. Talorys의 에이전트 Worker는 workers_dev: false와 preview_urls: false로 배포됩니다. 공개 URL이 아예 없습니다. 브라우저는 *.pages.dev 사이트하고만 통신하고, /api/*의 Pages Function이 서비스 바인딩을 통해 Worker로 요청을 전달합니다. 서비스 바인딩은 같은 계정 안 컴퓨트끼리 연결되는 Cloudflare 내부 사설 배선입니다. 인증과 권한 검사는 프런트엔드가 아니라 Worker에서 일어납니다.

이것이 무엇을 없애는지 생각해 보세요. 스캔할 공개 API 엔드포인트가 없습니다. 잘못 설정할 방화벽 규칙도, 패치를 잊을 리버스 프록시도 없습니다. 에이전트 백엔드의 공격 표면은 사실상 "프런트엔드의 인증 흐름을 반드시 거쳐야 한다"가 전부입니다. 예산과 보안 팀이 있는 실제 회사의 프로덕션 배포를 검토한 적이 있는데, 그중 몇몇은 이 사이드 프로젝트보다 네트워크 보안 상태가 느슨했습니다. 제가 클라우드 환경에서 본 가장 흔한 사고 패턴은 "내부에서만 접근 가능"하던 내부 서비스가 어느 날 갑자기 외부에 열리는 것이었습니다. 누군가 로드밸런서 설정을 잘못 건드렸기 때문이죠. 서비스 바인딩은 실수로 공개 상태가 될 수 없습니다. 애초에 URL이 존재하지 않으니까요.

설치 프로그램도 언급할 만합니다. 소유자 비밀번호를 로컬에서 PBKDF2-SHA256으로 해싱하고 해시만 Cloudflare 시크릿으로 저장하며, 256비트 세션 시크릿을 생성하고, 리소스 이름을 고유하게 짓습니다. 그리고 AI 추론 할당량을 쓰지 않은 채로 실제 배포를 검증합니다. 인증되지 않은 요청이 실제로 거부되는지까지 확인하죠. 배포 후에 인증이 비인증 요청을 실제로 막는지 확인하는 점검은 제가 사후 분석 보고서에 적어 넣던 종류의 것입니다. 명령 한 줄짜리 설치 프로그램에 이게 들어 있는 걸 보니 기분 좋은 충격이었습니다.

보호된 관문 하나와, 내부 다리로만 닿을 수 있는 밀봉된 안쪽 방을 가진 요새.
공개 관문은 하나, 공개 백엔드는 없음: 서비스 바인딩 덕분에 에이전트 Worker에는 공격할 URL이 없습니다.

무료 티어에서 살아남되 스스로를 속이지 않기

무료 티어는 실제로 존재하지만, 뷔페가 아니라 예산입니다. Cloudflare는 무료 계정에 "Neurons"라는 단위로 측정되는 일일 Workers AI 할당량, 즉 하루 10,000을 주고, 요청 및 Durable Object 사용량 쿼터도 줍니다. 이 숫자들은 Cloudflare가 정하며 바뀔 수 있습니다. Talorys는 이 문제를 제가 본 대부분의 "무료 티어" 프로젝트보다 솔직하게 다룹니다. AI 할당량이 소진되면 채팅이 그 사실을 분명히 알려 주고 일일 리셋 후 재개되며, 작업, 노트, 메모리, 리마인더는 모델이 필요 없으므로 계속 동작합니다.

이 안전장치들은 여러분의 프로젝트에도 가져다 쓸 만합니다. Talorys에는 최대 출력 토큰, 최대 컨텍스트 토큰(오래된 기록은 조용히 잘리지 않고 요약됨), 요청당 최대 도구 호출 및 추론 단계 수, 일일 최대 AI 요청 수, 일일 최대 예약 AI 실행 수에 대한 설정 가능한 상한이 들어 있습니다. 성숙한 접근입니다. 제한 없는 에이전트 루프는 청구서나 쿼터 장애로 아침을 맞게 만드는 원인이고, "에이전트가 도구를 40번 호출하기로 했다"는 실서비스에서 실제 돈을 태워 버리는 실패 패턴입니다. 관련해서, 이 프로젝트가 단순한 리마인더와 요약은 AI를 전혀 쓰지 않도록 한 선택도 정확합니다. 코드 경로가 결정적이라면 확률적 모델을 거칠 이유가 없습니다. 이는 모델 출력을 구조화된 결정으로 제약하는 것과 같은 원칙이며, 판단이 실제로 필요한 곳에만 모델을 쓰라는 뜻입니다.

커뮤니티의 실제 사례 하나를 주의로 덧붙입니다. 여러 사람이 Workers AI를 유료 Workers 플랜과 함께 쓸 때 불투명한 과금 놀람을 겪었다고 보고했습니다. Neuron 계산이 문서화된 한도와 맞지 않았고, 지원 티켓은 진전이 없었다고 합니다. 구체적인 내용은 확인할 수 없지만, 종량제 AI 과금이 어디서나 헷갈린다는 제가 잘 아는 패턴과 맞아떨어집니다. "무료 티어 친화적"이라는 말이 "과금될 수 없다"는 뜻은 아닙니다. 유료 플랜에서 배포한다면 첫날 Cloudflare 대시보드에 지출 알림을 설정하세요. 앱의 로컬 추정치가 아니라 제공자의 사용량 UI를 진실의 원천으로 삼으세요.

"셀프 호스팅" 싸움은 핵심을 놓치고 있다

이제 제 서두의 주장이 요구하는 양보를 하겠습니다. 이 프로젝트의 최상위 댓글은 "셀프 호스팅"이라는 단어를 두고 벌어진 불꽃 튀는 논쟁인데, 따지기 좋아하는 사람들에게도 일리가 있습니다. 이 프로젝트는 전적으로 Cloudflare에 의존합니다. 데이터는 그들의 데이터센터에 있고, 추론은 그들의 GPU에서 돌아가며, 내일 그들이 무료 티어를 바꾸면 여러분의 비서도 함께 바뀝니다. 이걸 셀프 호스팅이라 부르는 건 단어의 한계를 넘어서는 것입니다. 이미 통제하고 있는 Cloudflare 계정 외에는 제3자가 없고, 개발자에게 가는 텔레메트리도 없고, 중간에 운영자도 없다는 호의적인 해석은 "자기 소유" 또는 "자체 보관"이 더 맞는 표현입니다. 단어는 중요하고, 더 나은 단어를 골랐다면 이 프로젝트가 비판을 덜 받았을 겁니다.

하지만 여기서 따지기 좋아하는 사람들의 논리가 저에게는 설득력이 떨어집니다. 개인 소프트웨어의 실제 위협 모델은 "기업의 이용약관이 바뀔지도 모른다"가 아닙니다. 데이터 유출, 텔레메트리, 그리고 개발자가 파산해서 호스팅 버전을 닫아 버리는 일입니다. 이 세 가지 모두에서 Talorys는 꽤 강합니다. 분석이나 추적 코드가 없고, 개발자에게 무언가를 보고하는 것도 없으며, 닫을 호스팅 버전 자체가 없습니다. 게다가 코드는 MIT 라이선스이고, 한 댓글러가 지적했듯이 AI 호출을 로컬 모델 서버로 돌리도록 바꾸는 건 작은 패치면 됩니다. 전체 스택은 목 AI 제공자를 붙인 wrangler dev 아래에서 로컬로도 돌아갑니다. Cloudflare가 감당할 수 없게 되더라도 탈출 경로는 짧습니다. 누군가의 레지스트리에서 이미지를 받아오고 라이선스 확인을 위해 본사로 연락하는 Docker Compose 파일인 평균적인 "셀프 호스팅" 앱과 비교해 보세요.

댓글 속에 묻힌 더 공정한 비판도 있습니다. 이 비서가 실제로 좋은 제품이라는 증거는 없다는 점입니다. 채팅 인터페이스, 메모리 CRUD, 임베딩, 크론은 각각 만들기 쉽습니다. 개인 비서의 성패는 하네스, 프롬프트, 메모리 검색 정책, 도구 스키마에 달려 있는데, 깔끔한 아키텍처 다이어그램으로는 그런 부분이 입증되지 않습니다. 이 장르의 거의 모든 프로젝트가 그렇고, 그 점을 의심하는 건 옳습니다. Talorys는 검증된 코파일럿이 아니라, 잘 설계된 섀시, 즉 인프라로 평가하세요. 이런 것을 위해 모델을 고르고 있다면, 우리가 쓴 자신의 하드웨어에서 LLM 돌리기 글이 주권 스펙트럼의 반대편을 다룹니다.

자물쇠와 작은 구름을 달고 무게를 재는 황동 저울.
통제와 편의는 저울의 양쪽에 놓입니다. 솔직한 거래는 락인과 운영 부담 사이의 선택입니다.

이 설계에서 가져갈 것들

Talorys를 배포하지 않더라도 이 아키텍처는 내재화할 가치가 있는 참고 자료입니다. 전이 가능한 아이디어는 다음과 같습니다:

  1. 테넌트당 객체 하나. 앱이 단일 사용자이거나 깔끔하게 분할 가능하다면, 코드와 상태를 하나의 Durable Object에 함께 두고 캐시 계층, 메시지 큐, 커넥션 풀러를 지우세요.
  2. 공개 백엔드 URL은 없애기. workers_dev: false를 쓴 서비스 바인딩은 서버리스에서 가장 저렴한 보안 승리입니다. 도달할 수 없는 서비스는 공격받을 수도 없습니다.
  3. cron 서버 대신 알람. 인프라와 함께 사는 스케줄링은 재부팅, 재배포, 그리고 여러분 자신의 깜빡함에도 살아남습니다.
  4. 할당량 인지형 성능 저하. 비싼 의존성(모델)이 실패하거나 소진되더라도 핵심 제품은 계속 동작하도록 앱을 설계하세요. 성능 저하는 장애가 아니라 기능이어야 합니다.
  5. 추가 전용 마이그레이션. Durable Object 네임스페이스는 업데이트 시 다시 만들어지지 않고, 스키마 마이그레이션은 첫 요청에서 트랜잭션으로 실행됩니다. 이것이 데이터 손실 룰렛 없이 상태 있는 서버리스를 업데이트하는 방법입니다.

더 넓은 교훈은 개인 소프트웨어가 어디로 향하는지에 관한 것입니다. 20년 동안 선택지는 둘 중 하나였습니다. SaaS 회사에 데이터를 맡기거나, 파트타임 시스템 관리자가 되거나. Durable Object와, 다른 클라우드들이 불가피하게 내놓을 유사 기본 요소들은 세 번째 길을 엽니다. 나만 통제하는 소프트웨어를, 여러분이 임대한 인프라로 운영하고, 개인 규모에서는 비용도 들지 않는 방식이죠. 이건 셀프 호스팅이 아닙니다. 어쩌면 더 나을지도 모릅니다.