computerd라는 데몬이 capnweb RPC로 동기화한다. 같은 워크스페이스를 무거운 컨테이너로도, 가벼운 격리 워커로도 돌릴 수 있다는 뜻이다.
cloudflare/computer · npm @cloudflare/computer · 라이선스 MIT · 언어 TypeScript 376파일 ~13.2만 줄 · 테스트 144개(.test.ts, 소스당 1개 관례) · 모노레포 5패키지 + 예제 7개 · 핵심 의존 capnweb·just-bash·fuse-native·@platformatic/vfs · 상태 PREVIEW(프로덕션 부적합·API 불안정) · 최신 커밋 2026-08-03 · 버전 0.1.0-alpha.1(tag v0.1.1) · TrendShift Daily #8)
cloudflare/computer는 "AI 에이전트가 쓸 파일시스템을 Durable Object 안의 SQLite에 저장하고, 그 파일시스템 위에서 셸 명령과 코드를 실행하는 실행 환경을 컨테이너·격리 워커 중에 골라 끼울 수 있게 만든, Cloudflare 엣지 위의 에이전트용 컴퓨터"다. 한마디로 에이전트에게 내주는 "상태가 영구히 남는 작업용 컴퓨터"인데, 그 컴퓨터의 심장이 디스크가 아니라 데이터베이스라는 점이 남다르다.
콘솔 게임의 세이브 데이터가 본체가 아니라 클라우드에 있으면, 집 PS5에서 하던 걸 친구 집 기계에서 이어서 할 수 있다. 진짜 상태(세이브)는 클라우드에 있고, 기계는 그때그때 빌려 쓰는 "실행기"일 뿐이다.
computer가 딱 그 구조다. 에이전트의 파일들(세이브)은 항상 Durable Object의 SQLite(클라우드)에 있다. 실행이 필요하면 그 상태를 컨테이너에 FUSE로 마운트해 진짜 리눅스에서 git·pandoc을 돌리거나, 더 가벼우면 격리 워커에서 셸만 잠깐 켠다. 어느 실행기를 쓰든 파일의 원본은 한 곳(DO)이라, 컨테이너가 꺼져도 상태는 그대로 남는다.
사용자(에이전트 코드)가 파일을 쓰고 명령 하나를 돌릴 때 computer 안에서 벌어지는 일을 압축하면 이렇다:
여기서 결정적인 건 ①과 ⑤가 모두 SQLite를 갱신한다는 점이다. 호스트(DO)에서 쓴 card.md와 컨테이너의 pandoc이 만든 card.pdf가 같은 하나의 파일시스템으로 보인다 — 한쪽은 데이터베이스에 직접 쓰고, 다른 쪽은 FUSE를 통해 쓰지만, 둘 다 결국 같은 SQLite에 수렴한다. 이 "하나의 파일시스템" 착시를 만드는 게 이 레포의 전부라고 해도 된다.
첫째, 발상의 순서가 거꾸로다. E2B·Daytona·Modal 같은 기존 "에이전트 샌드박스"는 컴퓨터(컨테이너/VM)를 먼저 주고, 파일시스템은 그 안의 디스크다 — 컨테이너가 죽으면 상태도 날아간다. computer는 반대로 파일시스템(DO 안 SQLite)을 원본이자 진실로 두고, 실행 환경을 일회용 부속으로 갈아 끼운다. 그래서 컨테이너가 꺼져도, 심지어 실행 환경 없이도 파일은 그대로 남고 버전이 관리된다. 에이전트의 "기억"을 디스크가 아니라 데이터베이스에 두는 셈이다.
구글 독스 vs 워드 파일. 워드는 문서가 특정 노트북 안 파일이라, 그 노트북이 고장 나면 문서도 위태롭다(=컨테이너 디스크). 구글 독스는 문서가 클라우드에 살고, 브라우저는 그저 그때그때 보는 창이다. computer의 파일시스템은 구글 독스 쪽이다 — 실행기(컨테이너)는 문서를 잠깐 여는 창일 뿐, 원본은 항상 클라우드(DO)에 있다.
둘째, 하나의 워크스페이스, 여러 실행 표면(execution surface)이라는 유연함이다. 같은 파일시스템 위에서 상황에 맞게 실행기를 고를 수 있다 — 진짜 리눅스 바이너리(git, pandoc, npm)가 필요하면 컨테이너 백엔드, 셸 몇 줄이면 되는 가벼운 작업엔 격리 워커(just-bash), 구조화된 코드 실행엔 격리 자바스크립트 백엔드. 무거운 컨테이너를 항상 띄울 필요가 없어 비용·지연이 준다.
셋째, Cloudflare 엣지 원시요소(primitive)를 정직하게 조립했다는 공학적 매력이다. Durable Object(상태+SQLite), Containers(진짜 리눅스), Worker Loaders(동적 격리 실행), R2(오브젝트 스토리지) — 남의 인프라를 흉내 내지 않고 자사 블록을 레고처럼 끼워 "에이전트용 컴퓨터"를 만들었다. capnweb(용량 기반 RPC)로 DO와 컨테이너를 잇고, git 내부구현처럼 내용 주소 지정(content-addressed) 청크 저장소로 바뀐 조각만 골라 동기화한다.
파일시스템을 SQLite에 담자, 평범한 디스크에선 어려운 것들이 저절로 따라온다 — 영속성(컨테이너와 무관하게 상태 유지), 버전/개정(rev) 추적, 내용 기반 중복제거(같은 청크는 한 번만 저장), 델타 동기화(바뀐 청크 해시만 전송). "저장을 DB로 바꿨을 뿐인데 부수 효과로 얻는 능력"이 이 설계의 진짜 배당금이다.
README가 대문짝만하게 경고한다: "PREVIEW ONLY … NOT suitable for production use. APIs are unstable." 워크스페이스당 용량은 DO 저장소를 공유해 ~10GB가 상한이고, 컨테이너 쪽 파일시스템은 메모리에 얹혀 "에이전트 규모"(모노레포 통째 X)를 가정한다. 큰 순차 I/O는 FUSE 특성상 진짜 디스크보다 느리다. "배워두면 좋은 최신 설계"로 보되, 지금 프로덕션에 넣을 물건은 아니다.
전체가 TypeScript다(376파일, ~13.2만 줄). 프레임워크 런타임 의존은 놀랄 만큼 적다 — 공개 패키지 @cloudflare/computer의 런타임 의존은 acorn(JS 파싱), capnweb(RPC), just-bash(격리 셸) 단 셋이고, AI SDK 도구·Node VFS 제공자·zod는 전부 optional peerDependency라 쓸 때만 딸려온다. 무겁게 만들지 않으려는 절제가 보인다.
ReadableStream을 그대로 실어 보내 엔드투엔드 백프레셔(backpressure)가 커널 파이프까지 이어진다. computer에선 DO ↔ computerd 사이의 파일 동기화와 명령 실행 스트림을 나르는 배관이다./workspace에 진짜 폴더처럼 띄운다. just-bash는 순수 JS로 구현한 bash 호환 셸 — 진짜 리눅스 없이 격리 워커 안에서 셸 명령을 해석·실행한다.| 패키지 | npm 이름 | 역할 |
|---|---|---|
| computer | @cloudflare/computer | 유일한 공개 패키지. DO 쪽 얼굴 — Workspace, runtime 라우터, 백엔드들, git·assets·artifacts·tools 서브경로 |
| dofs | @cloudflare/dofs | SQLite 기반 VFS. fs/* 원시연산(read·write·mkdir·chunk), 스키마·마이그레이션, 동기화 빌딩블록 |
| rpc | @cloudflare/computer-rpc | capnweb 배선. 와이어 타입(SyncRPC·ShellRPC), server/client/driver/debug 헬퍼 |
| computerd | @cloudflare/computerd | 컨테이너 안 데몬. FUSE 마운트(fuse-native) + HTTP/WS capnweb 서버 + exec Runner. 소스 전용 |
| computerd-linux-x64 | (동명 npm) | 미리 빌드한 SEA 바이너리. 단일 레이어 컨테이너 이미지로 배포 — Dockerfile이 COPY --from |
worker-shell·worker-javascript 백엔드의 실행터). R2=S3 호환 오브젝트 스토리지(/workspace/r2 마운트·아티팩트 공유 링크). computer는 이 넷을 조립한다.sherif는 모노레포 의존성 버전 불일치를 CI에서 잡는다.rolldown-plugin-dts로 타입 선언을 뽑는다.esbuild로 computerd를 한 파일로 묶고 node --experimental-sea-config + postject로 실행 바이너리에 주입. 이걸 컨테이너 이미지로 배포한다.v*) 푸시가 릴리스를 트리거한다..agents/skills/에 capnweb·cloudflare·TDD·debugging-computerd-fuse 등 AI 에이전트용 지침(SKILL.md)과 AGENTS.md 관문을 둔다. 이 레포 자체가 "에이전트가 개발한다"를 전제로 짜였다.전체 그림은 세 꼭짓점의 삼각형이다. ① DO(Workspace) 안 SQLite가 파일의 원본, ② capnweb이 그 사이의 배관, ③ computerd(FUSE)가 컨테이너에서 진짜 파일시스템으로 투영. 컨테이너 백엔드일 때의 실제 배선은 이렇다:
재미있는 디테일 하나 — 세션을 컨테이너가 DO에게 되건다(reverse-dial). 보통은 바깥(DO)이 안(컨테이너)으로 연결하지만, 여기선 computerd가 부팅 후 ws://computer.internal/ws로 DO에 전화를 걸어 capnweb 세션을 연다. 컨테이너를 외부에서 직접 주소 지정하기 어려운 환경을 우아하게 피하는 방식이다.
모든 실행기는 WorkspaceBackend 한 인터페이스를 구현한다. 핵심은 connect(host)가 돌려주는 BackendHandle의 sync 필드다:
// 백엔드가 연결되면 돌려주는 핸들 (개념 요약)
interface BackendHandle {
rpc: WorkspaceRPC; // 이 백엔드의 computerd 를 가리키는 스텁
sync?: "remote" | "none"; // ★ 이 백엔드가 자기 저장소를 갖는가?
closed?: Promise<void>; // 전송 끊기면 resolve → 핸들 버리고 재연결
close(): Promise<void>;
}
sync:"remote"는 "이 백엔드는 자기만의 SQLite VFS가 따로 있다"는 뜻(컨테이너) — 그래서 DO와 push/pull로 상태를 오간다. sync:"none"은 "저장소를 새로 두지 않고 호스트 DO를 그대로 재사용한다"는 뜻(격리 워커 셸) — 되돌려 부를 두 번째 저장소가 없으니 동기화가 no-op이라 왕복이 사라진다. 같은 파일시스템 위에 "동기화가 필요한 실행기"와 "필요 없는 실행기"를 한 인터페이스로 나란히 꽂는 게 이 설계의 묘수다.
| 백엔드 | 소스 언어 | 동기화 | 필요 조건 |
|---|---|---|---|
| container-shell | 셸 명령 | remote(자체 SQLite VFS, capnweb 동기화) | Cloudflare Container + computerd, nodejs_compat |
| worker-shell | just-bash 명령 | none(같은 DO로 되부름) | Worker Loader 바인딩, experimental 플래그 |
| worker-javascript | ECMAScript 모듈 | none | Worker Loader, experimental, waitUntil |
WorkspaceRPC를 이룬다. SyncRPC=파일 동기화: push(DO→컨테이너), fetchChanges(컨테이너←DO), hasObjects/fetchObjects(해시로 없는 청크만 골라 가져오기), watermarks(싼 진단·하트비트). ShellRPC=명령 실행: exec(→ {id, events: 스트림}), getExec·killExec·disposeExec.SQLite 스키마(SCHEMA_VERSION 5)를 보면 이 파일시스템이 사실 git과 닮은 내용 주소 저장소임이 드러난다. 파일 바이트는 512 KiB 청크로 쪼개 해시를 키로 저장한다:
vfs_nodes(inode, type, mode, mtime, rev, manifest_hash, size ...) // 파일/폴더/심링크
vfs_dirents(parent_inode, name, child_inode) // 이름→inode (디렉터리)
vfs_chunks(inode, idx, hash, size) // 파일 = 청크들의 순서
vfs_blobs(hash, size) + vfs_blob_bytes(hash, bytes) // 해시→실제 바이트 (중복제거)
이 구조라서 동기화가 똑똑해진다 — DO가 "이 파일이 바뀌었고, 청크 해시는 A·B·C다"라고 보내면, 받는 쪽은 hasObjects로 "A·B는 이미 있음, C만 없음"을 답하고 C의 바이트만 전송한다. 같은 청크는 온 시스템에서 한 번만 저장되고(중복제거), 큰 파일을 조금 고쳐도 바뀐 청크만 오간다(델타 동기화). 대신 큰 파일을 쓸 때마다 청크를 해싱하는 비용이 붙어, 순차 대용량 I/O는 느려진다(§7 벤치 참고).
레고로 지은 집을 이사하기. 집을 통째로 트럭에 싣는 대신(=파일 전체 전송), 레고 블록으로 지었다면 "새로 바뀐 블록 몇 개"만 택배로 보내면 상대가 자기 집에 끼운다. 같은 모양 블록은 이미 갖고 있으니 안 보낸다. computer의 청크 동기화가 이 레고 이사다 — 그래서 빠르고 알뜰하지만, 블록을 매번 쪼개고 번호(해시) 매기는 수고가 든다.
구조에서 읽히는 힌트 셋. 첫째, 공개는 packages/computer 하나뿐이고 나머지 넷은 그 밑을 받치는 내부 부품이다 — 사용자는 @cloudflare/computer만 알면 되고, dofs·rpc·computerd는 몰라도 된다. 둘째, packages/computer/src/backends/ 아래 세 폴더가 §4의 세 백엔드와 정확히 대응한다. 셋째, git·assets·artifacts·tools 서브경로가 별도로 있는 건, 이 파일시스템 위에 git 저장소, R2 자산, AI SDK 도구까지 "에이전트가 실제로 쓸 살림살이"를 얹어 팔기 때문이다.
대부분의 서버리스는 "무상태(stateless)"라 상태를 매번 외부 DB에서 불러온다. Durable Object는 코드와 상태(SQLite)가 한 몸인 드문 모델이다. 이 레포는 DO를 "한 에이전트의 컴퓨터 한 대"로 쓰는 실전 예를 통째로 보여준다 — 서버리스에서 "영구히 켜져 있는 것처럼 보이는 상태"를 어떻게 얻는가를 코드로 배우는 최고의 교재다.
파일을 청크로 쪼개 해시를 키로 저장하고, 바뀐 청크만 골라 보내는 hasObjects/fetchObjects 흐름은 사실상 git의 object store와 packfile 협상을 파일시스템에 적용한 것이다. 중복제거·델타 전송·rev 추적이 왜 "저장을 내용 주소로 바꾸면 공짜로 따라오는가"를 실물로 이해하게 된다.
packages/computerd/src/fuse/는 "파일시스템도 그냥 프로그램"임을 가르친다. create/open/write/release 콜백을 구현하면 ls·cat·git이 그대로 동작한다. st_blocks·st_blksize를 손수 채워 du가 올바로 나오게 하는 디테일까지, POSIX 파일시스템 시맨틱을 바닥부터 만난다.
메서드 호출뿐 아니라 ReadableStream을 그대로 실어 보내 백프레셔가 양끝을 잇는 RPC. 명령의 stdout이 커널 파이프에서 클라이언트까지 밀림 없이 흐르는 구조, 원격 스텁을 using으로 수명 관리하는 패턴(아래 함정) 등, "네트워크 너머 객체를 로컬처럼 다루는" 최신 RPC 감각을 얻는다.
Biome(린트+포맷), sherif(의존성 일관성), 소스당 .test.ts 1개 관례(144개), SEA 단일 바이너리 빌드(esbuild+postject), OIDC 트러스티드 퍼블리싱, 그리고 .agents/skills/로 AI 에이전트가 이 레포를 개발하도록 규약을 문서화한 점까지 — 2026년식 프로덕션 TS 모노레포의 표준 장비를 한 번에 훑는다.
using을 잊지 말 것README가 못 박는다: capnweb의 원격 스텁은 자동 회수되지 않는다. getWorkspace(...)와 ws.runtime.exec(...)가 돌려주는 값은 using으로 받아 스코프를 벗어날 때 정리해야 한다. 반대로 순수 값 반환(readFile의 문자열, stat, readdir)은 스텁이 아니라 그냥 써도 된다. 이걸 놓치면 스텁이 새어 컨테이너 메모리가 부푼다.
| 쓰는 방식 | 필요한 것 |
|---|---|
| 컨테이너 백엔드 | Cloudflare Container(예: standard-2 = 1 vCPU·6 GiB·12 GB 디스크) + computerd 이미지, wrangler, compatibility_flags:["nodejs_compat"] |
| 격리 워커 셸/JS | Worker Loader 바인딩(LOADER), compatibility_flags:["nodejs_compat","experimental"]. 컨테이너 불필요 |
| 로컬 개발 | wrangler dev. 컨테이너에 /dev/fuse가 없으면 computerd가 유저스페이스 shim으로 자동 폴백(FUSE_MOUNT=auto) |
| 저장 한도 | 워크스페이스당 ~10 GB(DO 저장소 공유). 컨테이너 쪽 FS는 메모리 상주 — "에이전트 규모"용 |
computerd FUSE를 tmpfs·실제 디스크(ext4)와 비교한 수치. 비율은 computerd/기준값이라 1.0 미만이면 computer가 더 빠름:
| 작업 | vs 실제 디스크 | 해석 |
|---|---|---|
| 파일 1000개 stat | 0.91x | 메타데이터 위주는 디스크보다 빠름 — SQLite 인덱스가 파일 나열·삭제에 유리 |
| rm / mkdir 트리 | 0.66x / 0.74x | |
| git init+commit / clone | 0.72x / 0.84x | |
| 64 MiB 쓰기 / 복사 / 읽기 | 16.9x / 40x / 30x | 큰 순차 I/O는 크게 느림 — 청크마다 해싱·내용주소 저장 비용 |
| sandbox-sdk npm install (854패키지·36,675파일) | 124.7초 (디스크 63.9초·tmpfs 34.3초) | 디스크의 약 2배 — 파일 많은 설치는 손해 |
요약하면 "작은 파일 많이 만지는 일(git·빌드 메타·탐색)엔 강하고, 큰 파일 통째로 읽고 쓰는 일엔 약하다". 에이전트가 코드·문서를 조물조물 다루는 전형적 작업엔 잘 맞고, 대용량 미디어 처리엔 안 맞는다는 뜻이다.
examples/worker-shell을 wrangler dev로 띄우고, ws.fs.writeFile("/hello.txt", ...)로 파일을 쓴 뒤 ws.runtime.exec("cat /hello.txt")를 돌려 보자. 컨테이너 없이 격리 워커만으로 파일시스템+셸이 도는 걸 확인하는 게 첫걸음. exec 결과의 stdout·exitCode를 찍어 보고, 되었으면 ws.fs.readdir("/")로 방금 만든 파일이 보이는지 본다.
examples/think처럼 한 Workspace에 "shell"(worker-shell)과 "container" 백엔드를 둘 다 등록하고, 같은 파일을 만든 뒤 exec(cmd, {backend:"shell"})와 {backend:"container"}로 각각 실행해 보자. 컨테이너에서만 git --version·pandoc이 되는 이유(진짜 리눅스 vs just-bash)를 몸으로 이해하고, 첫 컨테이너 exec의 지연(lazy 부팅+동기화)을 측정한다.
packages/dofs의 스키마(vfs_nodes·vfs_chunks·vfs_blobs)를 읽고, 큰 파일 하나를 쓴 뒤 같은 내용을 복사해 보자. 청크 해시가 재사용(중복제거)되는지, 파일을 조금만 고치면 바뀐 청크만 새로 생기는지를 확인하면 §4의 델타 동기화가 왜 성립하는지 확실히 잡힌다. docs/19의 벤치 하네스도 함께 읽어 "왜 큰 쓰기가 느린가"를 수치로 대조.
packages/rpc/src/interface.ts와 packages/dofs/src/sync/를 따라가며, DO가 컨테이너로 push할 때와 컨테이너 변경을 fetchChanges로 되가져올 때 어떤 청크 해시가 오가는지 로그를 심어 관찰한다. watermarks가 하트비트로 쓰이는 지점, hasObjects로 "없는 것만" 고르는 협상을 눈으로 확인하면 분산 동기화 설계가 손에 잡힌다.
examples/tutorial을 본떠, LLM에게 createAITools({workspace})를 쥐여 주고 "/report.md를 쓰고 컨테이너에서 pandoc으로 PDF 변환해 R2 공유 링크를 반환"하는 에이전트를 만들어 보자. host에서 쓴 파일과 컨테이너 pandoc이 만든 PDF가 같은 파일시스템으로 보이는 순간을 직접 구현하는, 이 레포의 정수를 통째로 체험하는 과제다.
| 주차 | 주제 | 구체적으로 |
|---|---|---|
| 1주 | Durable Objects + Workers | DO의 단일 인스턴스·SQLite 저장소·alarm, Workers 요청 수명주기, wrangler 바인딩. packages/computer/src/workspace.ts를 표본으로 |
| 2주 | 내용 주소 저장 & 델타 동기화 | 청킹·해싱·중복제거·rev, git object store/packfile 협상과의 평행. packages/dofs/src/{schema,sync} |
| 3주 | FUSE & VFS | 사용자공간 파일시스템, POSIX 시맨틱(inode·dirent·stat), 쓰기 버퍼·release 커밋. packages/computerd/src/fuse/ |
| 4주 | capnweb & 용량 기반 RPC | 스텁·프로미스 파이프라이닝·스트리밍 백프레셔·수명관리(using). packages/rpc/와 capnweb 문서 |
| 5주(선택) | 에이전트 샌드박스 설계 | E2B·Daytona·Modal·cloudflare/sandbox-sdk 비교, MCP로 도구 노출, 안전한 코드 실행(격리·타임아웃·네트워크 정책) |
ws.fs.*로 파일을, ws.runtime.exec(source,{backend})로 명령/코드를 실행한다. exec가 유일한 실행 진입점이고, 백엔드에 따라 source가 셸 명령이거나 ESM 모듈이 된다./workspace에 마운트하고, capnweb 서버로 변경분을 DO와 동기화하며, 셸 명령을 실제 실행한다. SEA 바이너리로 컨테이너 이미지에 담겨 배포.using 필수.hasObjects 협상으로 없는 청크만 전송(델타 동기화)한다. git object store의 파일시스템판.git·pandoc이 그대로 동작한다.ws://computer.internal/ws로 "되전화"해 capnweb 세션을 여는 방식. 컨테이너를 외부에서 직접 주소 지정하기 어려운 환경을 우회한다.node --experimental-sea-config + postject로 주입. computerd를 npm(120MB) 대신 가벼운 컨테이너 이미지로 배포하려는 선택.@cloudflare/computerREADME.md(개요·빠른시작) · docs/10_project_layout.md(구조) · docs/16_code_execution.md(백엔드) · docs/19_performance.md(벤치) · AGENTS.md(개발 관례)fuse-native(FUSE 바인딩) · just-bash(JS 셸) · isomorphic-git