durable-harness: 실행 중 함수가 죽어도 Claude Code와 Codex, Pi의 턴을 로그에서 이어 가게 하는 어댑터 (feat. Vercel)

핵심 요약

  • Vercel이 2026년 10월 19일 블로그 글로 durable-harness를 공개했습니다. Claude Code, Codex, OpenCode, Pi 같은 코딩 에이전트 하네스의 세션을 내구성 있는 로그에 기록해, 턴 도중에 함수가 죽어도 새 함수가 로그를 읽고 그 턴을 이어 가게 하는 TypeScript 어댑터 모음입니다.
  • 끝난 모델 호출과 도구 호출은 로그에서 결과를 읽어 다시 실행하지 않습니다. 중단된 도구 호출은 도구마다 정한 재실행 규칙(replay: 'safe' 또는 'never')에 따라 처리합니다.
  • 세션은 오픈소스 Workflow SDK의 World 인터페이스 뒤에 저장되므로 Vercel, 로컬 파일, 직접 운영하는 Postgres 같은 저장소 중에서 고를 수 있습니다. 다만 Claude Code와 Codex는 셀프 호스팅에서도 세션마다 Vercel Sandbox 안에서만 실행됩니다.
  • npm 패키지는 0.0.1-beta.6입니다. Vercel은 저장소 README에서 실험 단계라 릴리스 사이에 API가 바뀔 수 있다고 밝히고 있습니다. Claude Code와 Codex 지원은 문서에서도 실험 기능으로 표시되어 있습니다.

durable-harness 소개

코딩 에이전트를 노트북에서 돌릴 때는 하네스(Harness)가 작업 디렉터리와 세션 기록을 같은 디스크에서 계속 읽을 수 있습니다. 작업이 끊겨도 사람이 상황을 보고 "이어서 해 줘"라고 다시 지시하면 됩니다. 하지만 에이전트를 클라우드에서 상시 실행하면 사정이 달라집니다. 대부분의 하네스는 진행 중인 턴(Turn)을 그 턴을 실행하는 프로세스의 메모리에 두기 때문에, 서버리스 함수가 시간 제한에 걸리거나 메모리가 부족해 죽으면 그 턴도 함께 사라집니다. VM과 영구 저장소를 붙여도 어떤 모델 호출과 도구 호출이 끝났는지 기록하고 크래시 뒤 턴을 다시 시작하는 일은 자동으로 이뤄지지 않습니다. 이번에 소개하는 durable-harness는 이 빈자리를 메우기 위해 Vercel이 공개한 어댑터와 유틸리티 모음입니다.

durable-harness는 하네스 자체를 고치지 않고, 하네스가 하는 일을 밖에서 기록합니다. 모델 호출이나 도구 호출을 실행하기 전에 호출 의도(Intent)를 로그에 남기고, 호출이 끝나면 결과(Outcome)를 남깁니다. 턴 하나마다 큐 메시지가 하나씩 걸려 있습니다. 턴을 실행하던 함수가 죽으면 큐가 그 턴을 새 함수에 넘기고, 새 함수는 로그를 읽어 멈춘 지점부터 이어 갑니다. 브라우저 연결이 끊겨도 에이전트는 계속 실행되고, 다시 접속한 클라이언트는 로그에서 놓친 이벤트를 따라잡습니다. Vercel은 이 구조가 Workflow SDK를 움직이는 것과 같은 기반 위에 만들어졌다고 설명합니다.

지원 대상은 Pi, OpenCode (:pytorch::kr: Opencode: 다양한 LLM을 사용할 수 있는, 터미널 기반의 오픈소스 AI 코딩 에이전트), Codex, Claude Code이고, Vercel이 만든 Zig 기반 코딩 에이전트 하네스 fx용 도구도 포함됩니다. 같은 회사의 에이전트 프레임워크 eve (:pytorch::kr: eve: 파일시스템을 저작 인터페이스로 삼는 AI 에이전트 프레임워크 프로젝트 (feat. Vercel))와 fx는 처음부터 내구성을 갖추고 있으므로, durable-harness는 그렇지 않은 하네스에 같은 성질을 덧붙이는 역할을 합니다. Vercel은 블로그에서 OpenWork처럼 오픈소스 하네스를 엔진으로 쓰는 앱과, 직접 하네스 기반 서비스를 만드는 개발자를 사용 사례로 들고 있습니다. 패키지는 Node.js 22 이상이 필요하고(Agent Runs 연동만 Node.js 24), 각 하네스의 공식 패키지를 피어 의존성(Peer Dependency)으로 함께 설치해 사용합니다.

durable-harness와 기존 방식 비교

내구성 실행(Durable Execution)은 워크플로 엔진 분야에서 오래 다뤄진 문제이고, 코딩 에이전트에도 이미 비슷한 시도가 있습니다. 가장 가까운 것은 durable-harness의 권장 Pi 연결 방식(durable-harness/pi-durable)이 그대로 쓰는 Pi Durable입니다. Pi Durable은 모델 요청과 도구 호출을 체크포인트를 남기는 태스크로 실행하고, 같은 replay: "safe" 규칙으로 도구 재실행을 정합니다. 다만 SQLite나 JSONL 같은 저장소를 한 번에 한 프로세스만 소유하고 프로세스 사이의 잠금은 없어서, 여러 서버로 나눠 실행하려면 소유권을 애플리케이션이 직접 조율해야 합니다. durable-harness는 Pi Durable의 저장소를 World 위에 올리고 큐를 붙여, 다른 머신의 함수가 세션을 넘겨받을 수 있게 합니다.

각 방식의 차이를 정리하면 다음과 같습니다:

항목 로컬 하네스 VM + 영구 저장소 Pi Durable 단독 durable-harness
턴 상태를 두는 곳 프로세스 메모리와 로컬 디스크 프로세스 메모리와 VM 디스크 저장소(SQLite, JSONL 등)의 체크포인트 World의 이벤트 로그
크래시 뒤 재개 사람이 다시 지시 자동 재개 없음 같은 저장소를 다시 연 프로세스가 재개 큐가 턴을 새 함수에 넘기고 로그에서 재개
여러 서버 해당 없음 직접 구성 한 저장소는 한 프로세스만 소유 세션마다 한 서버만 쓰기 가능, World가 나머지 쓰기를 거부
대상 하네스 각 하네스 각 하네스 Pi Pi, OpenCode, Claude Code, Codex, fx 도구

표에서 로컬 하네스와 VM의 재개 방식은 Vercel 블로그의 설명이고, 두 칸의 나머지 항목은 편집자가 정리한 것입니다. Pi Durable 칸은 Pi Durable README의 제약을 정리했습니다. durable-harness가 Vercel 인프라와 얼마나 묶여 있는지는 하네스마다 다르며, 아래 셀프 호스팅 절에서 따로 다룹니다.

durable-harness를 사용하면 좋을 사용자

durable-harness가 맞는 경우는 코딩 에이전트를 여러 사용자에게 서비스로 제공하면서, 함수 시간 제한이나 재배포 때문에 턴이 끊기는 문제를 겪는 팀입니다. 이미 Next.js와 Vercel로 앱을 운영하고 있다면 withDurableHarness 설정 한 줄과 채팅 라우트로 큐 라우트까지 구성되고, Agent Runs 화면에서 세션별 모델 호출과 도구 호출을 추적할 수 있습니다. 결제, 이메일 발송처럼 두 번 실행되면 안 되는 도구를 에이전트에게 맡기는 경우에도 replay: 'never' 규칙과 로그 기록 순서가 도움이 됩니다.

Vercel은 로컬 하네스 작업을 내구성 있게 만드는 것도 사용 사례로 들지만, 한 사람이 터미널에서 Claude Code나 Codex를 직접 쓰는 경우라면 얻는 것이 적습니다. 그 사용 방식에서는 사람이 중단을 보고 다시 지시하는 것으로 충분하고, durable-harness는 이 두 하네스를 로컬 개발에서도 Vercel에서 실행되는 Sandbox 안에서만 실행하므로 Sandbox 사용 요금이 생깁니다. API 안정성이 필요한 프로덕션 서비스, 그리고 Claude Code나 Codex를 Vercel 없이 운영하려는 팀에게는 durable-harness가 아직 적절한 선택지가 아닙니다. 버전이 0.0.x 단계이고, 두 하네스는 셀프 호스팅에서도 Vercel Sandbox가 필요하기 때문입니다.

durable-harness의 동작 원리: 의도와 결과를 기록하는 로그와 큐

durable-harness에서 세션 하나는 Workflow SDK의 실행(Run) 하나입니다. 대화 내용, 진행 중인 턴, 모든 모델 호출과 도구 호출이 그 실행의 이벤트 로그에 들어갑니다. Workflow SDK는 로그를 저장하는 Storage, 끊긴 턴을 다시 배달하는 Queue, 지켜보는 클라이언트에게 이벤트를 보내는 Streamer를 World라는 하나의 인터페이스로 묶어 두었습니다. durable-harness는 이 인터페이스에만 의존합니다:

import type { Queue, Storage, Streamer } from '@workflow/world'
interface World extends Storage, Queue, Streamer {}

아래 그림은 Vercel 문서의 인터랙티브 데모에서 CI 대기 도중 함수가 시간 제한에 걸린 경우를 캡처한 것입니다. 왼쪽 Without durability 쪽은 턴 상태가 프로세스 메모리에 있어 "Timed out. Stopped until someone notices" 상태로 멈춥니다. 오른쪽 With durable-harness 쪽에서는 function 2가 앞의 네 단계를 already in the log로 읽어 오고, 중단된 CI 대기만 다시 실행한 뒤 답변을 이어 갑니다:

함수가 오류로 실패하면 다른 함수가 즉시 턴을 이어받습니다. 메모리 부족처럼 흔적 없이 사라지면, 큐 메시지의 임대(Lease) 기간이 끝난 뒤 큐가 턴을 다시 배달합니다. 어느 경우든 재개는 로그를 기준으로 하며, Vercel이 문서에서 설명하는 규칙은 다음과 같습니다:

  • 결과가 로그에 있는 호출은 다시 실행하지 않고 결과를 읽어 옵니다. Vercel은 블로그에서 이 덕분에 끝난 모델 호출의 토큰 비용이 두 번 청구되지 않는다고 설명합니다.
  • 실행 중이던 모델 호출은 다시 실행합니다. 비용에 대한 별도 언급은 없지만, 다시 보낸 요청만큼 토큰을 다시 쓴다고 보는 것이 자연스럽습니다(편집자 해석).
  • 크래시 직전에 끝났지만 아직 로그에 기록되지 않은 호출은 실행 중이던 호출로 취급합니다.
  • 실행 중이던 도구 호출은 그 도구의 재실행 규칙을 따릅니다.

재실행 규칙은 하네스에 따라 동작이 조금 다릅니다. Vercel 블로그는 replay: 'safe'로 표시한 도구가 다시 실행된다고 요약하지만, 문서의 표를 보면 자동으로 다시 실행하는 하네스는 Pi뿐입니다:

규칙 크래시로 중단된 호출의 처리
'safe' Pi는 다시 실행합니다. Claude Code, Codex, OpenCode는 다시 실행해도 안전하다고 모델에게 알리고, 모델이 판단합니다
'never' 두 번 실행하지 않습니다. 모델에게는 호출이 실행됐을 수도 있다고 알려, 다시 시도하기 전에 확인할 수 있게 합니다

단, Vercel Sandbox의 Drive에서 파일을 바꾸던 명령은 크래시로 중단되면 일부만 실행됐을 수 있으므로, Pi에서도 'safe' 여부와 관계없이 다시 실행하지 않고 중단 사실을 모델에게 알립니다.

기록 순서도 하네스별로 다릅니다. durability: 'on'이면 재실행해도 안전한 호출은 로그 기록을 기다리지 않고 바로 시작하며, durable-harness/pi의 기본값입니다. Claude Code, Codex, OpenCode는 모든 호출 전에 로그 기록을 기다리는 durability: 'strict'가 기본입니다. 'never' 호출은 어느 하네스에서든 로그 기록이 끝난 뒤에 실행됩니다. 도구 쪽 멱등성(Idempotency)은 여전히 개발자 몫이어서, Vercel은 환불이나 이메일처럼 부수 효과가 있는 도구에 'never'를 지정하고 호출하는 외부 시스템에도 멱등 키(Idempotency Key)를 넘기라고 권고합니다. 블로그에 실린 예제는 조회 도구와 환불 도구에 각각 다른 재실행 규칙을 지정합니다:

import { pi } from 'durable-harness/pi';
import type { HostTool } from 'durable-harness';
import { getOrder, refundOrder } from './orders'; // your own code

const tools: HostTool[] = [
  {
    name: 'get_order',
    description: 'Looks up an order',
    replay: 'safe', // safe to run again after a crash
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
    execute: async ({ id }) => JSON.stringify(await getOrder(id)),
  },
  {
    name: 'refund_order',
    description: 'Refunds an order',
    replay: 'never', // never runs twice
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
    execute: async ({ id }) => JSON.stringify(await refundOrder(id)),
  },
];

export const agent = pi({ model: 'anthropic/claude-sonnet-5.5', tools });

Claude Code, Codex, OpenCode처럼 자체 세션 파일을 쓰는 하네스는 그 기록도 로그에 복제됩니다. Claude Code의 대화 기록(Transcript), Codex의 롤아웃(Rollout) 파일, OpenCode v2의 이벤트 저장소가 여기에 해당하며, 새 함수나 새 Sandbox는 로그에서 이 파일을 다시 만듭니다.

이 형식은 각 CLI의 내부 형식이라 durable-harness는 하네스 버전을 하나로 고정합니다. 문서 기준으로 Claude Agent SDK 0.3.296, Codex 0.160.0, OpenCode 패키지 2.0.22(와 effect 4.0.0-rc.112)입니다. Codex는 Sandbox에 함께 들어 있어 따로 설치하지 않습니다. Claude Agent SDK나 OpenCode 패키지를 다른 버전으로 설치하면 턴은 설치해야 할 버전을 알려 주며 실패하고, 세션은 그대로 유지됩니다. OpenCode는 effect 버전까지 고정하므로 이미 다른 버전의 effect에 의존하는 앱에서는 아직 쓸 수 없고, 실행 중인 세션이 메모리를 많이 쓰므로 턴을 실행하는 함수에 메모리를 넉넉히 줘야 합니다.

durable-harness의 하네스별 실행 위치와 보안 경계

Vercel의 함수는 Fluid compute로 한 인스턴스가 여러 요청을 동시에 처리하므로, 다른 사용자의 세션이 같은 프로세스에서 실행될 수 있습니다. 그래서 durable-harness는 "함수에서는 개발자가 작성한 코드만 실행하고, 모델이 고른 명령과 파일은 가상 워크스페이스나 Vercel Sandbox에서 실행한다"는 규칙을 둡니다. 모든 하네스는 도구가 없는 상태로 시작하므로, 에이전트는 개발자가 넘겨준 도구만 쓸 수 있습니다. 하네스별 실행 위치는 다음과 같습니다:

하네스 하네스가 실행되는 곳 명령과 파일
Pi (두 가지 방식 모두) 개발자의 함수 직접 작성한 도구, 가상 워크스페이스, Vercel Sandbox
fx 개발자의 함수 직접 작성한 도구, 가상 워크스페이스, Vercel Sandbox
OpenCode 개발자의 함수 직접 작성한 도구, 가상 워크스페이스, Vercel Sandbox
Claude Code (실험) Vercel Sandbox 전용 직접 작성한 도구, 또는 builtins: true로 켠 자체 도구
Codex (실험) Vercel Sandbox 전용 직접 작성한 도구, 또는 builtins: true로 켠 자체 도구

Claude Code와 Codex만 Sandbox에 가둔 이유를 Vercel은 이렇게 설명합니다. Pi, fx, OpenCode는 한 프로세스에서 여러 세션을 처리하도록 만든 라이브러리이지만, Claude Code와 Codex는 한 사용자를 위한 CLI이고 상태를 디스크에 둡니다. 지금 버전은 세션끼리 분리할 수 있어도 다음 버전이 한 세션의 상태를 다른 세션에 남기지 않는다는 보장이 없다는 것입니다. 그래서 durable-harness는 두 CLI를 세션마다 별도 Sandbox에서만 실행하고, 그 밖의 곳(함수나 직접 운영하는 서버)에서는 실행을 거부합니다.

Vercel Sandbox 안에는 비밀 값이 들어가지 않습니다. CLI의 모델 요청은 함수로 돌아와 AI Gateway로 전달되고, 세션 로그도 함수가 씁니다. 세션 파일은 Sandbox보다 오래 남는 Drive의 /workspace에 있어서, 세션이 새 Sandbox로 옮겨도 파일과 대화가 이어집니다. Vercel이 문서에서 직접 밝힌 한계도 있습니다:

  • Sandbox는 기본적으로 모든 외부 통신을 허용하므로, 프롬프트 인젝션에 걸린 모델이 자기 세션의 파일을 외부로 보낼 수 있습니다. sandbox({ network: 'deny-all' }) 또는 허용 호스트 목록으로 막아야 합니다.
  • Codex는 builtins: true일 때 자체 샌드박스를 시작하지 못하면 Sandbox 안에서 전체 권한으로 명령을 실행합니다. 이때 모델의 명령이 함수를 거쳐 개발자의 AI Gateway 크레딧으로 모델을 직접 호출할 수 있습니다(설정한 budget 범위 안).
  • 모델 제공자가 실행하는 웹 검색 같은 도구는 Sandbox의 네트워크 설정과 상관없이 외부에 접속합니다.
  • Sandbox에 넘긴 환경 변수는 모델의 명령이 읽을 수 있습니다.
  • UI가 모델 출력 속 원격 이미지를 그대로 렌더링하면, 클릭 없이도 모델이 고른 URL로 브라우저가 요청을 보내는 유출 경로가 됩니다.
  • Sandbox 시간은 세션당 기본 4시간으로 제한되지만, 모델 사용 비용은 sandbox({ budget })을 지정하지 않으면 상한이 없습니다. Vercel은 AI Gateway만 호출할 수 있는 AI_GATEWAY_API_KEY를 따로 쓰라고 권장하는데, 지정하지 않으면 프로젝트의 OIDC 토큰으로 모델 호출을 인증하고 그 토큰은 World와 Sandbox API에도 접근할 수 있기 때문입니다.

채팅 라우트도 비용과 바로 연결됩니다. 프로덕션에서는 authorize 없이 만든 채팅 라우트가 모든 요청을 401로 거부합니다. 누구나 쓰게 열려면 authorize: 'public'에 더해, Sandbox를 실행하는 에이전트는 allowPublicSandbox: true와 유한한 sandbox({ budget: { sessionUsd } })를 함께 지정해야 합니다. sessionUsd는 모델 호출이 durable-harness를 거치는 Claude Code와 Codex에만 적용되고, Pi와 OpenCode는 vmSeconds나 AI Gateway 예산으로 따로 제한해야 합니다. AI Gateway 예산은 시스템 자격 증명으로 과금되는 사용분에만 적용되고 BYOK 사용분은 세지 않습니다. 로그인했다고 모든 세션에 접근할 수 있는 것은 아니므로, Vercel의 하네스별 가이드는 withSessionOwnership 헬퍼로 세션 소유자를 저장하고 요청마다 확인하는 패턴을 씁니다.

durable-harness의 파일과 문서(Document)

에이전트에게 셸과 파일을 줄 때는 세 가지 워크스페이스 중 하나를 고릅니다. 가상 워크스페이스는 just-bash라는 bash 호환 셸을 함수 안의 가상 파일시스템 위에서 실행하며, 함수의 파일, 프로세스, 환경 변수에는 접근할 수 없습니다. Vercel 문서에 정리된 차이는 다음과 같습니다:

항목 virtual() virtual({ durable: false }) sandbox()
명령이 실행되는 곳 함수 안의 just-bash, 가상 파일시스템 같음 세션 전용 VM
크래시 뒤 파일 세션 로그에 보존, 중단된 호출은 흔적을 남기지 않음 턴이 끝날 때마다 삭제 Drive에 보존, 중단된 명령은 일부만 실행됐을 수 있음
비용 함수 CPU 시간과 로그 저장 함수 CPU 시간 Sandbox와 Drive 사용량, 첫 사용 시 Sandbox 시작
적합한 작업 짧은 작업과 작은 워크스페이스 한 턴 안의 임시 작업 실제 프로젝트와 오래 걸리는 작업

가상 워크스페이스에는 node나 python 같은 실제 바이너리와 네트워크가 없고, 워크스페이스 하나에 파일이 약 500개까지 들어갑니다. 패키지 설치나 빌드가 필요한 작업은 Sandbox로 보내야 합니다.

문서(Document)는 세션 로그에 저장되는 타입이 있는 JSON 상태입니다. 할 일 목록, 계획, 에이전트가 채우는 양식처럼 대화에는 속하지 않지만 세션과 함께 다녀야 하는 상태를 담습니다. 도구가 io.docs로 문서를 고치면 그 변경이 도구 결과와 함께 커밋되므로, 크래시로 중단된 호출은 문서를 바꾸지 않습니다. 앱은 별도 데이터베이스 없이 agent.sessions.doc()으로 같은 문서를 읽고, 채팅 라우트에 docs를 지정하면 변경 사항을 브라우저로 스트리밍할 수 있습니다. 문서 하나는 최대 64KB이며, 앞에서 본 virtual() 워크스페이스도 파일 트리를 이 문서 기능 위에 저장합니다.

문서는 개발자가 넘긴 도구만 다룰 수 있어 Claude Code와 Codex의 자체 도구에는 보이지 않고, 모델이 읽게 하려면 docTools()로 읽기 도구와 수정 도구를 만들어 줘야 합니다. durable-harness/pi-durable은 이 기능 대신 Pi 자체의 문서 기능을 씁니다.

durable-harness의 셀프 호스팅과 Vercel 의존 범위

durable-harness는 Workflow SDK와 같은 방식으로 World를 고릅니다. Vercel에서는 Vercel의 World, 그 밖의 환경에서는 .workflow-data 폴더에 파일로 저장하는 로컬 World를 쓰고, WORKFLOW_TARGET_WORLD를 지정하면 그 World를 씁니다. 로컬 World는 개발용이나 디스크가 유지되는 단일 서버용이고, 여러 서버를 운영하거나 데이터베이스에 세션을 두려면 Postgres World를 씁니다. Vercel 블로그는 MongoDB, Redis, Turso World도 언급하지만, durable-harness의 셀프 호스팅 문서가 설정 절차를 다루는 것은 Postgres World입니다. @workflow/world-postgres 5.0.4 이상을 설치하고 테이블을 만드는 bootstrap 명령을 한 번 실행한 뒤, 환경 변수 두 개를 지정하면 fx를 제외한 에이전트의 저장소가 Postgres로 옮겨집니다:

WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"

fx는 이 변수를 읽지 않으므로, libfx/durable-world의 world()에 직접 만든 World를 코드로 넘겨야 합니다.

같은 데이터베이스에 서버를 여러 대 연결하면 서버마다 큐를 소비하고, World는 한 세션을 한 번에 한 서버만 실행하게 합니다. 두 번째 서버가 세션을 넘겨받으면 첫 번째 서버의 다음 쓰기를 World가 거부하므로 첫 번째 서버는 멈춥니다. 이 부분이 단일 프로세스 소유를 전제로 한 Pi Durable과 다른 점입니다. 다만 이 차단은 로그 쓰기에만 적용됩니다. 저장소의 후속 작업 목록에 따르면, 멈추지 않고 계속 실행되던 이전 서버가 보낸 라이브 스트림 이벤트는 아직 막히지 않아 재개된 응답 사이에 섞일 수 있습니다. Vercel은 셀프 호스팅 문서에 몇 가지 주의 사항도 적어 두었습니다:

  • Postgres World는 참조 구현이라 라우트를 인증하지 않고 데이터를 암호화하지 않으며, 대규모 처리에 맞춰 조정되어 있지 않습니다. 큐 라우트는 리버스 프록시나 방화벽으로 직접 보호해야 합니다.
  • 서버는 시작할 때 찾은 모든 열린 세션을 재개하므로, 앱마다 데이터베이스와 워커를 따로 둬야 합니다. 에이전트를 만들지 않는 별도 큐 워커 프로세스는 forwardQueue(world)로 큐를 앱의 큐 라우트에 전달해야 합니다.
  • Postgres World는 큐 워커를 오래 실행되는 프로세스에서 돌리므로 Vercel Functions에서는 동작하지 않습니다.
  • Claude Code와 Codex는 직접 운영하는 서버에서도 Vercel Sandbox에서만 실행됩니다. 서버가 턴을 구동하고 로그를 쓰되, CLI는 Vercel 액세스 토큰으로 실행한 Sandbox 안에서 돌아갑니다.

정리하면 Pi, OpenCode, fx는 세션 저장과 명령 실행을 Postgres World와 가상 워크스페이스로 Vercel 밖에 둘 수 있고, Claude Code와 Codex는 저장소만 옮길 수 있습니다. 모델 호출은 별개입니다. 문서의 예제는 모두 Vercel의 AI Gateway로 모델을 호출하고 Vercel 밖에서는 AI_GATEWAY_API_KEY를 발급해 쓰라고 안내하며, 직접 준비한 모델 제공자를 쓰는 방법은 durable-harness/pi-durable 예제의 주석(// or your own pi-ai models)에만 나옵니다. AI Gateway 자체는 Vercel 밖의 서버에서도 API 키로 호출할 수 있고 BYOK(Bring Your Own Key)로 자체 제공자 계정을 연결할 수도 있지만, 요청은 Vercel이 운영하는 게이트웨이를 거칩니다. Vercel에서 실행하면 Agent Runs 화면에서 세션별 추적 결과를 볼 수 있습니다. 이 기능은 베타이고 Node.js 24와 프로젝트의 트레이싱 샘플링 규칙이 필요하며, 베타 기간에는 Always-on Tracing 요금으로 과금됩니다.

durable-harness 설치와 사용

README의 빠른 시작은 Next.js 앱에서 Pi를 연결하는 예제입니다. 먼저 durable-harness와 Pi Durable 관련 패키지를 함께 설치합니다:

npm install durable-harness @earendil-works/pi-durable @earendil-works/chord @earendil-works/pi-ai just-bash

Pi의 내구성 Harness를 World 기반 저장소 위에서 열고, Pi의 코딩 도구를 가상 워크스페이스에 연결합니다. 아래는 README의 코드 그대로이며, README 예제의 모델 ID는 anthropic/claude-sonnet-4.5이고 문서와 블로그 예제는 anthropic/claude-sonnet-5.5를 씁니다:

// lib/agent.ts
import { BACKGROUND_CONTEXT as ctx } from '@earendil-works/chord/context';
import { createRegistry, Harness, type Storage } from '@earendil-works/pi-durable';
import { CodingTools, GATEWAY_PROVIDER, gatewayModels, resumeWith, virtualEnv } from 'durable-harness/pi-durable';

const models = gatewayModels();
const registry = createRegistry();
registry.install(CodingTools);

export const agent = { model: { provider: GATEWAY_PROVIDER, modelId: 'anthropic/claude-sonnet-4.5' } };
export const open = resumeWith((storage: Storage) => Harness.open(storage, { models, registry, env: virtualEnv() }, ctx));

채팅 라우트를 만들고, next.config.ts를 withDurableHarness로 감싸면 큐 라우트와 그 트리거가 함께 구성됩니다:

// app/api/chat/route.ts
import { chatRoute } from 'durable-harness/pi-durable';
import { agent, open } from '@/lib/agent';

export const { GET, POST } = chatRoute({ open, agent, authorize: async (request) => { /* your auth */ } });
// next.config.ts
import type { NextConfig } from 'next';
import { withDurableHarness } from 'durable-harness/next';

const nextConfig: NextConfig = {
  // your existing config
};

export default withDurableHarness(nextConfig);

Pi에는 두 가지 연결 방식이 있고, Vercel은 위 예제의 durable-harness/pi-durable을 권장합니다. Pi 1.0의 Harness를 그대로 쓰므로 확장, 압축, 포크 기능이 유지되고, 기존 Pi 코드는 저장소만 바뀝니다. 대신 단계마다 로그 기록을 기다려 턴이 느려지고, 다른 요청에서 보낸 조향(Steering) 메시지가 세션을 넘겨받으며, 재접속하면 진행 중인 응답을 처음부터 다시 받습니다.

세션이 미완료 작업을 둔 채 recoverAfterMs(기본 120초) 동안 아무것도 커밋하지 않으면 크래시로 보고 다른 함수가 넘겨받을 수 있으므로, 출력 없이 오래 걸리는 도구는 이 값을 늘려야 합니다. Pi의 에이전트 루프만 필요하면 지연이 적고 다른 하네스와 같은 방식으로 동작하는 durable-harness/pi를 씁니다.

Claude Code는 claudeCode() 팩토리 하나로 시작하며, 기본값으로 세션마다 Vercel Sandbox를 씁니다. 도구가 하나도 없어도 세션마다 Sandbox를 시작하므로 Sandbox 시간과 Drive 저장 용량이 과금됩니다. 자체 도구(Bash, Read, Edit, Write, NotebookEdit, WebFetch)는 builtins: true로 켜야 하고, 각 호출은 실행 전에 로그에 기록되며 크래시 뒤에는 다시 실행되지 않습니다. Claude Code 가이드의 설치 명령과 에이전트 정의는 다음과 같습니다:

npm i durable-harness @anthropic-ai/claude-agent-sdk@0.3.296 @vercel/sandbox zod
import { claudeCode } from 'durable-harness/claude-code';

export const agent = claudeCode({
  model: 'anthropic/claude-sonnet-5.5',
  builtins: true,
});

한 가지 알려진 제약이 있습니다. durable-harness/pi, OpenCode, Claude Code, Codex의 턴은 채팅 라우트의 요청 안에서 시작해 최대 60초 동안 실행된 뒤 큐 라우트로 넘어가는데, 이 시점에 실행 중이던 명령은 다시 실행되지 않고 중단됩니다. Vercel은 이것이 Vercel Queues의 현재 한계이며 수정 중이라고 밝혔습니다. 그전까지는 설치나 빌드처럼 오래 걸리는 명령을 쓰는 에이전트에 agent.chatRoute({ drive: 'queue' })를 지정해 큐 라우트가 턴 전체를 실행하도록 하라는 것이 Vercel의 안내입니다. Codex, OpenCode, fx, 셀프 호스팅 서버 구성은 하네스별 가이드에 정리되어 있습니다.

durable-harness의 라이선스

durable-harness는 Apache-2.0 라이선스로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다.

:books: durable-harness 공식 문서 (Vercel Agent Harnesses)

:scroll: durable-harness 소개 블로그 (Making every harness durable)

:github: durable-harness 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

:pytorch:파이토치 한국 사용자 모임:south_korea:이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일:love_letter:로 보내드립니다! 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로도 새 글 알림을 받으실 수 있습니다. :smiley:

:wrapped_gift: 아래:down_right_arrow:쪽에 좋아요:+1:를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ :star_struck: