Cloudflare OS: AI 에이전트를 위한 운영체제

Cloudflare OS: AI 에이전트를 위한 운영체제

에이전트에게 작업을 맡겨두고 커피를 타러 다녀온 적 있으신가요? 돌아와 보면 첫 단계에서 승인 창을 띄워놓고 얌전히 기다리고 있죠. 😅 이런 일이 몇 번 반복되면 손이 가는 곳은 대체로 정해져 있습니다. 자동 승인을 켜거나, 권한 설정을 통째로 건너뛰는 옵션을 쓰거나.

안전하게 쓰라고 만든 장치가 오히려 위험한 선택을 부추기는 셈인데요. 개인이 쓰는 코딩 에이전트에서도 이런데, 회사 전체가 에이전트에 GitHub, Google Docs, Slack을 물려준다고 생각하면 문제는 훨씬 커집니다.

Cloudflare가 2026년 8월에 공개한 Cloudflare OS는 이 문제를 정면으로 다룹니다. 사내에서 수천 명이 실제로 쓰던 AI 작업 환경을 Apache-2.0 라이선스로 통째로 열어젖힌 프로젝트인데요. 이 글에서는 Cloudflare OS가 어떤 구조로 만들어졌고, 에이전트에게 권한을 주는 방식이 기존과 어떻게 다른지 살펴보겠습니다.

어떤 의미의 운영체제일까요

이름부터 짚고 가는 게 좋겠습니다. Cloudflare OS는 여러분의 노트북에 설치하는 그런 운영체제가 아닙니다. 저장소의 README는 “운영체제”라는 말을 두 가지 의미로 쓴다고 밝히고 있는데요. 하나는 회사가 AI로 안전하게 일하기 위한 운영 체계라는 뜻이고, 다른 하나는 전통적인 운영체제가 연산 워크로드를 관리하듯 AI 워크로드를 관리한다는 뜻입니다.

두 번째 의미는 생각보다 진지합니다. 저장소 구조를 일반적인 운영체제와 나란히 놓고 보면 대응이 꽤 정확하게 맞아떨어집니다.

일반 운영체제Cloudflare OS
커널packages/workshop-backend
장치 드라이버packages/gatekeeper-*
packages/workshop-frontend
프로세스가젯(gadget)
실행 파일블루프린트(blueprint)
사용자사용자
접근 제어 목록공유 권한
???에이전트

마지막 줄의 물음표가 이 프로젝트의 문제의식을 압축해서 보여줍니다. 전통적인 운영체제에는 에이전트에 대응하는 개념이 없거든요. Cloudflare는 이걸 기존 운영체제에 빠진 기능이라고 봅니다. 에이전트는 사용자로 취급할 수 없습니다. 사람에게 책임을 지면서도 자기만의 제한된 권한을 가져야 하고, 무엇보다 코드 조각을 즉석에서 써서 실행하는 방식으로 일하니까요. 이런 대상에게는 접근 제어 목록보다 능력 기반 보안(capability-based security)이 맞다는 게 이들의 판단입니다.

가젯: 모두가 자기 앱을 소유합니다

Cloudflare OS에서 슬라이드를 만들면 클라우드 어딘가의 SaaS를 호출하는 게 아닙니다. 나만을 위한 슬라이드 앱 인스턴스가 새로 생깁니다. 이 인스턴스를 가젯이라고 부르는데요. 남들의 슬라이드와는 완전히 다른 샌드박스에서 돌아갑니다.

이게 왜 중요할까요? 우선 슬라이드 앱에 보안 버그가 있어서 내 자료가 공격자에게 새어 나가는 일이 구조적으로 불가능해집니다. 애초에 다른 사람의 데이터가 같은 프로세스 안에 존재하지 않으니까요. 그리고 코드를 마음대로 고칠 수 있습니다. 원하는 기능이 없으면 에이전트에게 추가해달라고 하면 그만입니다.

지난 25년간의 클라우드 아키텍처와 정반대 방향인데요. 모두가 같은 서버에 접속하는 대신, 모두가 자기 복사본을 실행하는 쪽으로 돌아간 것입니다. 누구나 에이전트를 시켜 필요한 기능을 붙일 수 있게 된 시대에는 소프트웨어를 중앙에 모아둘 이유가 줄어든다는 게 이 설계의 전제입니다.

실행 구조는 Cloudflare Workers 위에 올라가 있습니다. 워크스페이스 하나가 Durable Object 하나에 대응하고, 가젯은 그 안에서 Dynamic Worker Facet으로 실행됩니다. Facet은 Durable Object를 더 잘게 쪼개는 단위라서, 가젯마다 런타임이 관리하는 것과는 분리된 자체 SQLite 데이터베이스를 갖게 됩니다. Dynamic Worker는 가벼운 V8 아이솔레이트(isolate)를 쓰기 때문에 앱마다 전용 서버나 컨테이너를 띄우지 않고도 격리된 런타임을 하나씩 나눠줍니다.

flowchart TB
    accTitle: Cloudflare OS의 실행 구조
    accDescr: 브라우저의 샌드박스 iframe이 Cap'n Web RPC로 워크스페이스 안의 가젯 서버와 통신하고, 가젯은 게이트키퍼를 거쳐야만 외부 서비스에 접근할 수 있다.

    subgraph browser["브라우저"]
        client["가젯 클라이언트<br/>(샌드박스 iframe)"]
    end

    subgraph workspace["워크스페이스 (Durable Object)"]
        direction TB
        server["가젯 서버<br/>(Dynamic Worker Facet)"]
        db[("SQLite")]
        gk["게이트키퍼"]
    end

    external["GitHub, Google,<br/>Slack, Notion..."]

    client -- "Cap'n Web RPC" --> server
    server --- db
    server -- "능력 바인딩" --> gk
    gk -- "OAuth + 정책" --> external

클라이언트와 서버는 반드시 Cap’n Web RPC로 통신해야 한다는 제약이 걸려 있는데요. Cloudflare가 만든 오픈소스 원격 프로시저 호출(remote procedure call, 이하 RPC) 시스템입니다. 이 제약 덕분에 재미있는 일이 벌어집니다. 서버가 알아서 깔끔한 API를 노출하게 되니까, 별도로 MCP 서버를 만들거나 에이전트 루프를 붙이지 않아도 에이전트가 그 앱을 조작할 수 있게 됩니다. 앱을 만들어달라고 한 다음, 그 앱 안에서 에이전트와 함께 작업하는 게 기본으로 딸려오는 셈이죠.

게이트키퍼: 외부 서비스를 위한 장치 드라이버

앞의 대응표에서 게이트키퍼는 장치 드라이버 자리에 있었습니다. 드라이버가 프로그램과 하드웨어 사이에 앉듯이, 게이트키퍼는 Cloudflare OS와 외부 서비스 사이에 앉는 서비스별 Worker입니다.

저장소에 이미 들어 있는 게이트키퍼만 봐도 GitHub, Google, Slack, Notion, Linear, Confluence, Supabase, Spotify, Home Assistant, 이메일, 스케줄러 정도가 있고, MCP 서버를 붙이기 위한 것도 따로 있습니다. 각 게이트키퍼가 맡는 일은 다섯 가지입니다.

  • API 래핑: 서비스가 원래 제공하는 API를 감싸서 Cap’n Web API로 깔끔하게 정리합니다.
  • 인가 처리: OAuth 흐름을 대신 밟고 자격 증명을 보관합니다.
  • 범위 제한: 사용자가 의도한 리소스에만 접근이 닿도록 좁힙니다.
  • 행동 기록: 가젯이나 에이전트가 한 모든 행동을 남깁니다.
  • 사람 승인: 부수 효과가 있는 행동에는 사람이 승인하거나 거절할 기회를 만듭니다.

세 번째 항목이 실무에서 특히 와닿습니다. 에이전트에게 GitHub 계정 전체를 열어주는 건 아무래도 과하죠. 게이트키퍼는 저장소 하나로 범위를 좁히거나, 이슈는 읽되 소스 코드는 못 읽게 하거나, 특정 필드를 가리거나, 호출 빈도를 제한할 수 있습니다.

MCP를 써보셨다면 익숙한 그림일 텐데요. README도 게이트키퍼를 “강화판 MCP 서버”라고 소개합니다. 기존에 조직에서 운영하던 MCP 서버가 있다면 포털을 통해 그대로 붙일 수 있고요. 다만 권한을 다루는 방식에서 결정적인 차이가 있습니다.

기본값은 접근 권한 제로

Cloudflare OS에서 에이전트와 가젯은 기본적으로 아무것도 할 수 없습니다. 관리자가 GitHub 계정을 연결해뒀더라도 마찬가지입니다. 연결해둔 것과 쓸 수 있는 것은 별개거든요.

권한을 주려면 리소스를 하나씩 “소개”해야 합니다. 저장소 링크를 붙여 넣거나 UI에서 골라주는 식이죠. 에이전트 쪽에서 필요하다고 판단한 리소스를 요청할 수도 있고, 그러면 사람이 허락하거나 거절합니다.

이렇게 소개받은 리소스는 생성된 코드에 타입이 붙은 바인딩으로 전달됩니다.

const issues = await env.PROJECT.listIssues({
  teamId: "ENG",
  state: "open",
});

여기서 env.PROJECT는 자격 증명이 아닙니다. 특정 정책 아래 특정 리소스를 쓸 수 있다는 권한 자체를 나타내는 능력(capability)이고, 실제 토큰은 에이전트로부터 완전히 격리된 채 게이트키퍼 안에만 남습니다. 에이전트가 코드를 아무리 창의적으로 써도 손에 쥔 게 능력뿐이라면 그 능력이 허용하는 범위를 벗어날 수 없죠.

대부분의 에이전트 환경이 MCP 서버를 미리 등록해두고 모든 대화에서 전체 접근 권한을 항상 열어두는 것과 대비됩니다. 소개 방식은 에이전트를 지금 이 작업에 실제로 필요한 만큼으로 묶어둡니다.

승인을 기다리지 않는 사람 개입 장치

개인적으로 가장 흥미로웠던 부분입니다. 앞에서 이야기한 승인 피로 문제를 이들은 아예 다른 각도에서 풉니다.

전통적인 사람 개입(human-in-the-loop) 방식은 동기적입니다. 에이전트가 뭔가 하려다 멈추고, 사람이 승인 버튼을 누를 때까지 기다립니다. 그래서 커피를 타러 간 사이에 한 발짝도 못 나간 채로 멈춰 있는 일이 벌어지고, 결국 자동 승인으로 도망가게 되죠.

게이트키퍼는 승인이 필요한 행동이 들어오면 결과를 로컬에서 시뮬레이션합니다. 에이전트에게는 작업이 완료됐다고 알려주고, 나중에 결과를 다시 읽으려 하면 시뮬레이션된 결과를 돌려줍니다. 에이전트는 멈추지 않고 다음 작업을 계속 쌓아 올립니다. 사람은 나중에 편할 때 한꺼번에 승인하거나 하나씩 골라 거절하면 됩니다.

말로만 들으면 위태로워 보이는데, 실제 구현을 보면 꽤 단순합니다. GitHub 게이트키퍼가 이슈를 만드는 코드를 볼까요?

packages/gatekeeper-github/src/github.ts
async createIssue(options: GitHubCreateIssueOptions): Promise<GitHubIssue> {
  const action = await this.#gatekeeper.prepareCreateIssue(options);
  await this.#gatekeeper.submitActionForApproval(this.#approvalQueue, action, {
    title: `Create issue ${options.title}`,
    description: `Create a new issue in ${action.owner}/${action.repo} titled "${options.title}".`,
    implementsRevert: false,
  });
  return new GitHubIssueImpl(
    this.#gatekeeper,
    this.#approvalQueue.dup(),
    action.provisionalId,
    "issue",
  );
}

이슈를 실제로 만들지 않고 준비만 한 다음 승인 큐에 넣습니다. 그리고 provisionalId, 즉 임시 식별자를 가진 객체를 곧바로 돌려줍니다. 에이전트 입장에서는 이슈가 생긴 것처럼 보이니 이 객체에 라벨을 붙이거나 댓글을 다는 후속 작업을 이어갈 수 있고, 그 작업들 역시 같은 큐에 쌓입니다.

승인 큐의 인터페이스 주석에는 설계 의도가 못 박혀 있습니다. submitAction은 즉시 반환하지만 실제 행동은 한참 뒤에야 수행될 수 있으며, 사용자가 몇 시간이나 며칠 뒤에 승인하더라도 아무 문제가 없어야 한다고요.

flowchart TB
    accTitle: 비동기 승인 흐름
    accDescr: 에이전트가 요청한 행동을 게이트키퍼가 준비만 하고 승인 큐에 넣은 뒤 임시 결과를 돌려주면, 에이전트는 멈추지 않고 다음 작업을 이어가며, 사람은 나중에 큐를 한꺼번에 승인하거나 거절한다.

    agent["에이전트가<br/>행동 요청"] --> prepare["게이트키퍼가<br/>준비만 수행"]
    prepare --> provisional["임시 결과<br/>반환"]
    provisional --> next["다음 작업<br/>계속"]
    next -.-> agent

    prepare --> queue[/"승인 큐<br/>적재"/]
    queue --> human{"사람이<br/>나중에 검토"}
    human -- "승인" --> apply(["서비스에 반영"])
    human -- "거절" --> discard(["폐기"])

에이전트를 세우지 않으면서도 부수 효과는 사람이 최종 결정한다는 점에서, 속도와 통제 사이의 흔한 절충을 제법 영리하게 비껴간 설계라고 봅니다. 그렇다고 감독의 부담이 사라지는 건 아닙니다. 쌓인 행동을 훑어보고 판단하는 일은 여전히 사람 몫이고, 여기에는 에이전트를 제대로 감독할 수 있는가라는 더 근본적인 질문이 따라붙습니다.

관찰한 것이 공유를 결정합니다

가젯을 동료에게 공유할 때 생기는 문제도 있습니다. 내가 만든 대시보드에 내 권한으로 읽어온 민감한 데이터가 들어 있다면, 그 대시보드를 공유하는 순간 권한이 없는 사람에게 데이터가 넘어가니까요.

Cloudflare OS는 가젯이 게이트키퍼를 통해 읽은 모든 것을 관찰(observation)로 기록해서 이걸 막습니다. Alice가 만든 가젯을 Bob이 열면, Bob은 각 게이트키퍼에 자기 계정을 연결해야 합니다. 그러면 게이트키퍼가 Bob의 계정으로 그동안의 관찰을 전부 직접 읽을 권한이 있는지 검증하고, 부족하면 접근을 거부합니다.

검증을 통과하면 Bob은 그 가젯의 관찰자로 등록되는데요. 이후로는 등록된 관찰자 중 한 명이라도 직접 읽을 수 없는 새 관찰이 발생하면 그 관찰 자체가 차단됩니다. Bob이 가젯을 열 때마다 권한을 다시 확인하기도 하고요. 각 서비스의 접근 제어 규칙을 아는 건 결국 그 서비스의 게이트키퍼뿐이니, 판단도 게이트키퍼의 신뢰 영역 안에서 이뤄집니다.

인터넷이 끊긴 채로 실행됩니다

가젯 격리는 서버와 클라이언트 양쪽에 걸쳐 있습니다.

서버는 Dynamic Worker로 로드되는데 인터넷 접근이 아예 꺼져 있습니다. 명시적으로 지정한 바인딩을 통해서만 바깥과 통신할 수 있고요. 클라이언트는 샌드박스 iframe에서 돌아갑니다. 이 iframe은 부모 프레임에 postMessage()로 열린 Cap’n Web 세션으로만 자기 서버와 대화할 수 있고, 그 밖의 인터넷 접근은 콘텐츠 보안 정책(Content Security Policy)과 iframe 샌드박스 설정으로 브라우저가 허용하는 최대한까지 차단됩니다.

에이전트가 만든 코드를 신뢰하지 않는다는 전제를 코드 리뷰가 아니라 실행 환경으로 관철한 것인데요. 격리된 실행 환경을 다루는 방식이라는 점에서 Cloudflare SandboxClaude Code의 샌드박싱과 문제의식이 닿아 있습니다.

앱이 아니라 코드를 나눕니다

만든 가젯을 남들도 쓰게 하고 싶다면 방법이 두 가지입니다.

가젯 자체를 공유하면 문서를 공유하듯 실시간 협업이 됩니다. 같은 상태를 함께 보고 서로의 작업이 즉시 반영되는데, Durable Object가 바탕에 깔려 있어서 시키지 않아도 코딩 에이전트가 알아서 구현해준다고 합니다.

블루프린트를 공유하면 상대가 자기 복사본을 새로 찍어냅니다. 블루프린트에는 소스 코드 스냅샷과 어떤 바인딩이 필요한지에 대한 설명, 제목 같은 메타데이터가 담깁니다. 반대로 SQLite에 저장된 데이터와 채팅 및 편집 이력, 그리고 자격 증명과 살아 있는 연결은 담기지 않습니다. 링크로 공유하거나 .gadget 파일로 내보내 다른 Cloudflare OS 인스턴스로 옮길 수도 있고요.

이게 사소해 보여도 클라우드 소프트웨어의 관행에서는 꽤 큰 변화입니다. 웹 앱을 만들어 공유한다면 보통 내 서버에 올려두고 사람들을 접속시키죠. 블루프린트는 오히려 모바일 앱이나 예전 PC 소프트웨어에 가깝습니다. 각자 자기 복사본을 돌리니 만든 사람이 서비스를 운영할 필요가 없고, 무엇보다 쓰는 사람이 소프트웨어를 직접 고칠 수 있습니다. 기능 요청을 넣고 우선순위에 밀리기를 기다리는 대신 에이전트에게 시키면 되니까요.

직접 돌려보기

로컬에서 전체 스택을 띄워볼 수 있습니다. pnpm만 설치돼 있으면 명령 하나로 끝납니다.

git clone https://github.com/cloudflare/cloudflare-os.git
cd cloudflare-os
pnpm run-local

의존성 설치와 프론트엔드 빌드를 거쳐 로컬 서버가 뜹니다.

결과
> pnpm install
Scope: all 27 workspace projects
Already up to date

> pnpm --filter @gadgets/workshop-frontend exec vite build
vite v7.3.6 building client environment for production...
 5896 modules transformed.

 Starting local server...
[wrangler:info] Ready on http://localhost:8787

wrangler와 workerd 위에서 돌아가고, 데이터는 .wrangler 하위 디렉토리에 쌓입니다. 프로덕션용은 아니지만 제품이 무엇을 하는지 확인하기에는 충분합니다. 슬라이드를 만들어달라거나 협업 화이트보드 앱을 만들어달라고 시켜보라는 게 README의 제안입니다.

자기 Cloudflare 계정에 배포하고 싶다면 배포 페이지를 쓰거나, 게이트키퍼 구성과 코드 수정까지 얹으려면 cloudflare-os-starter 저장소를 참고하면 됩니다. 이 배포용 저장소는 핵심 코드를 패치하지 않고 그대로 가져다 쓰는 구조라서, 설정과 UI 변경, 내부 연동, 분석, 배포 파이프라인을 자기 쪽에 두고 관리할 수 있습니다. workerd가 오픈소스인 덕분에 Cloudflare 밖의 자체 서버에서 돌리는 길도 열려 있는데, 이쪽 문서와 도구는 아직 준비 중입니다.

다만 기대치는 조정해둘 필요가 있습니다. 공개된 저장소는 1세대에서 얻은 교훈을 바탕으로 완전히 새로 쓴 2세대이고, 2026년 8월 시점에서 초기 접근(early access) 단계임을 스스로 밝히고 있습니다. 외부 기여도 지금은 소규모 버그 수정만 받고 있고요. Cloudflare 대시보드에서 쓰는 완전 관리형 제품, 개발 워크플로우를 위한 컨테이너 지원, Slack 같은 채팅 도구와의 통합이 다음 계획으로 예고돼 있습니다.

마치며

Cloudflare OS를 훑어보면서 가장 기억에 남은 건 에이전트를 사용자로 취급하지 말자는 주장이었습니다. 에이전트에게 사람 계정을 그대로 물려주고 접근 제어 목록으로 관리하려 드는 순간, 우리는 승인 지옥과 전권 위임 사이에서 하나를 고르게 되죠. 능력 기반 보안은 이 양자택일 자체를 다시 짜자는 제안입니다. 기본값은 아무 권한 없음으로 두고, 필요한 리소스만 하나씩 소개하고, 자격 증명은 게이트키퍼 안에 가둬둔 채 능력만 건네주는 식으로요.

승인을 시뮬레이션해서 에이전트를 세우지 않는 방식도 다른 도구들이 참고할 만한 아이디어라고 생각합니다. 사용자가 위험한 옵션으로 도망가는 이유가 게을러서가 아니라 기다림이 실제로 비용이기 때문이라는 진단은 정확하니까요.

물론 이 전제가 모든 조직에 맞지는 않을 겁니다. 모두가 자기 앱 복사본을 굴리는 모델은 중앙에서 통제해야 하는 업무와는 잘 맞지 않을 수 있고, 초기 접근 단계라는 점도 감안해야 합니다. 그래도 Workers 런타임을 만든 팀이 자기 런타임을 어떻게 쓰는지 통째로 들여다볼 수 있다는 것만으로도 코드를 읽어볼 가치는 충분합니다. 모델 선택과 비용 관리는 AI Gateway에, 사용자 인증은 Cloudflare Access에 맡기는 식으로 기존 제품들을 엮어 쓰는 방식도 함께 볼 수 있습니다.

더 자세한 내용은 Cloudflare OS 발표 글을 참고하세요.

This work is licensed under CC BY 4.0CCBY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord