← 목록으로

Pi Durable - 중단된 작업을 이어가는 AI 에이전트 하네스

요약
  • Pi Durable은 중단된 작업을 이어갈 수 있는 AI 에이전트용 실험적 하네스 프레임워크입니다.
  • 작업 상태를 저장해 프로세스 종료 후에도 중단 지점부터 재개할 수 있으며, 여러 대화의 동시 실행과 분기, 사용자 협업 기능을 제공합니다.
  • 장시간 실행되는 AI 에이전트 애플리케이션을 만들기 위한 실험적 하네스로, 터미널 코딩 에이전트 Pi 1.0과 별도로 공개
  • 작업 상태를 저장해 프로세스가 종료돼도 중단된 지점부터 재개하며, 도구 호출은 재실행해도 안전하다고 지정한 경우에만 자동으로 반복함
  • 여러 대화를 동시에 실행하거나 기존 대화에서 분기할 수 있으며, 대화마다 모델·도구·지침·실행 환경을 따로 지정 가능
  • 프롬프트·도구·작업 흐름을 확장할 수 있고, 실행 중 코드를 바꿔도 다음 호출부터 적용돼 진행 중인 작업을 유지하며 수정 가능
  • 여러 사용자가 같은 에이전트의 진행 상황을 함께 보고 지시할 수 있으며, 오래된 대화는 백그라운드에서 압축하되 원본 기록은 보존함
Pi 코딩 에이전트와 별개의 하네스
  • Earendil과 Pi 커뮤니티는 오랜 안정화, 유지보수, 개발을 거친 Pi 1.0과 함께 Pi Durable을 공개함
  • 기존 Pi 코딩 에이전트는 한 사람이 로컬 또는 원격 머신의 터미널에서 사용하며, 프로세스가 종료되면 사람이 상황을 확인하고 재개를 지시하는 방식에 집중함. 이 역할은 바뀌지 않음
  • Pi Durable은 다양한 접점에서 접근하고, 여러 사람이 같은 에이전트를 조종하며, 장기 대화와 심각한 내부/외부 장애를 견디는 애플리케이션을 위한 프레임워크임
    • 코딩 에이전트도 구축할 수 있지만 기존 제품을 대체하지 않음
    • pi-ai 등의 코드뿐 아니라 최소주의와 변경 용이성이라는 원칙도 공유함
    • 별도 프레임워크에서 설계를 실험하고, 유용성이 확인된 경험은 Pi 코딩 에이전트에 반영할 계획임
하네스의 구성과 코드 규모
  • 하네스(harness)는 저장소와 하나 이상의 LLM 대화를 병렬 실행하는 장치, 모델이 호출하는 도구, 도구가 동작하는 실행 환경을 합친 것임
  • 대화는 사용자와 에이전트의 상호작용을 기록한 것이며, 에이전트는 LLM에 사고 수준 등의 설정과 호출 가능한 도구를 결합한 것임
    • 실행 환경은 노트북, 원격 VM, 메모리 내 샌드박스 등이 될 수 있음
    • 어떤 도구와 실행 환경을 사용할지는 대화별로 정함
    • 모델 호출부터 도구 실행까지 하네스가 수행하는 모든 동작은 작업임
  • 에이전트가 자체 코드를 이해할 수 있도록 설계했으며, 테스트를 제외한 전체 소스는 약 15,000줄임
    • 전체를 읽으면 GPT 기준 약 150,000토큰, Claude 기준 약 250,000토큰임
    • 실제 개발에서 전체 소스가 필요한 경우는 드물며, 저장소 백엔드 약 3,000줄은 대개 건너뛸 수 있음
JavaScript 런타임에서 장시간 실행
  • 현재 실행 대상은 JavaScript 런타임이 있는 환경이며, 하네스는 저장소 백엔드 위에서 동작함
  • 기본 저장소로 메모리, SQLite, JSONL을 제공하며, 자체 백엔드를 위한 적합성 테스트 모음과 벤치마크도 포함함
    • SQLite와 JSONL 저장소 코드는 Node API를 사용하지 않아 작은 어댑터를 붙이면 Bun이나 Cloudflare Durable Object에서도 실행 가능함
    • 작은 저장소 인터페이스를 구현해 키-값 저장소나 Postgres에 연결할 수 있음
    • 한 번에 하나의 프로세스가 저장소를 소유하고, 다른 클라이언트는 그 프로세스에 접속함
  • SQLite에서는 활성 대화 기록, 실행 중인 작업, 대기 중인 제출처럼 현재 필요한 데이터만 메모리에 유지하고 나머지는 필요할 때 디스크에서 읽음
    • 오래된 메시지를 압축하므로 활성 대화 기록은 모델의 컨텍스트 창 범위로 제한됨
    • 수만 개 메시지가 있는 대화도 이 방식으로 메모리에 수용함
  • 파일이나 셸이 필요한 도구는 실행 환경을 통해 접근하며, 기본 NodeExecutionEnv는 로컬 파일 접근을 제공함
    • 원격 실행 환경을 구현하면 하네스와 도구를 서로 다른 머신에서 실행할 수 있음
    • env 함수는 도구 호출마다 대화의 작업 디렉터리를 바탕으로 환경을 구성하므로 대화별 실행 위치를 다르게 둘 수 있음
    • 기본 코딩 도구 묶음은 read, write, edit, bash를 제공함
프로세스 종료와 재시작 복구
  • 각 실행 단계는 다음 단계로 넘어가기 전에 체크포인트를 저장함. 새 프로세스가 같은 저장소를 열면 미완료 작업을 찾아 마지막 체크포인트부터 재개함
    • 노트북 절전, 컨테이너 재배포, 메모리 부족으로 인한 프로세스 종료 등을 대상으로 함
    • 중단된 모델 요청은 다시 보내고, 부분 응답은 대화 기록에 중단 표시와 함께 남김
    • 도구 호출은 안전한 경우에만 재실행하며, 그렇지 않으면 중단 사실을 모델에 전달함
  • 대기 중인 메시지는 그대로 유지되고, requestId 로 제출을 정확히 한 번 처리함
    • 클라이언트가 장애 후 같은 요청을 다시 제출해도 새 요청을 만들지 않고 원래 제출을 돌려줌
    • 이 보장은 제출에 대한 것이며, 모든 도구 호출을 무조건 재실행한다는 뜻은 아님
  • 내장 서브에이전트는 없지만 별도 대화로 구현할 수 있음
    • 서브에이전트 대화도 중단 지점부터 이어짐
    • 안전하게 재실행 가능한 서브에이전트 도구는 기존 서브에이전트를 다시 찾아 응답을 기다릴 수 있음
동시 대화와 분기
  • 하나의 하네스에서 여러 대화를 서로 막지 않고 동시에 실행하며, 각 대화에 동일한 내구성 보장을 적용함
  • 대화는 새로 시작하거나 기존 기록의 어느 지점에서든 분기할 수 있음. 분기 지점까지의 부모 기록은 복사하지 않고 참조함
    • Slack 채널을 하나의 대화로 두고, 특정 메시지에 달린 스레드를 그 지점에서 분기한 대화로 구성할 수 있음
    • 채널과 스레드는 동시에 진행되며 서로 실행을 막지 않음
  • 각 대화는 모델, 사고 수준, 선택한 확장, 활성 도구, 추가 지침, 실행 환경의 작업 디렉터리를 별도로 저장함
    • 주 에이전트 옆의 검토 에이전트에는 저렴한 모델, 읽기 전용 도구, 별도 체크아웃을 줄 수 있음
확장과 시스템 프롬프트
  • 확장(extension) 은 이름이 있는 시스템 프롬프트 섹션, 도구, 훅, 작업의 묶음임
    • 애플리케이션이 레지스트리에 설치하고 대화별로 사용할 확장과 도구를 선택함
    • 대화에는 구현 코드가 아니라 이름만 저장함
  • 시스템 프롬프트는 매 요청 전에 선택된 확장의 섹션으로 다시 구성하므로 변경 사항이 다음 요청에 반영됨
    • AGENTS.md나 스킬 정보를 실행 환경에서 읽고, 백그라운드에서 파일을 감시해 최신 상태를 반영하는 구성이 가능함
    • 변경 사항과 변경 위치를 대화 기록에 남겨 재시작하거나 분기해도 모델이 보았던 내용을 재현함
    • 대화 중간의 시스템 프롬프트와 도구 변경을 지원하는 모델에는 변경분만 보내 프롬프트 캐시를 유지함
도구, 서브에이전트, 도구 교체
  • 모든 도구 호출은 별도의 내구성 작업으로 실행하며, 실행 전에 호출 의도를 저장함
    • replay: "safe"를 선언한 검색 도구는 장애 후 재실행할 수 있음
    • 재실행을 허용하지 않은 배포 도구는 반복하지 않고, 중단 사실과 그때까지 저장한 출력을 모델에 전달해 후속 행동을 결정하게 함
    • 도구 출력은 관찰 중인 모든 클라이언트에 스트리밍할 수 있음
  • 도구는 하네스 API로 기록과 문서를 커밋하고, 작업과 대화를 시작하며, 다른 대화와 통신할 수 있음
    • 대화별 도구 선택으로 Slack 스레드에는 검색만 허용하고 배포는 제외할 수 있음
  • 서브에이전트는 도구 호출이 소유하는 대화를 만들고 작은 모델과 별도 지침을 지정한 뒤 응답을 기다리는 방식으로 구현함
    • 이슈 분류 예제는 도구 없는 gpt-6-luna에 bug, feature, question 중 하나로 답하도록 지시함
    • 재실행 시 소유 작업 ID로 기존 대화를 찾고, 동일한 requestId를 사용해 중복 제출을 피함
    • 서브에이전트는 자체 비용을 집계하며, UI에서 해당 도구 호출 아래에 표시할 수 있음
  • 뒤에 선택된 확장이 같은 이름의 도구를 제공하면 기존 도구를 교체함
    • Python 가상 환경 안에서 실행하는 bash로 바꾸는 식으로 활용 가능함
    • wrapTool은 최종 선택된 도구를 감싸며, 어떤 bash가 선택되든 실행 시간을 측정하는 확장을 만들 수 있음
훅의 개입과 재실행 규칙
  • 훅(hook) 은 모델 응답 생성, 도구 호출, 대화 압축 작업에 개입함
    • 모델 요청 수정, 도구 호출 차단/수정, 결과 교체, 실행 연장, 직접 요약 작성이 가능함
    • 장애 후 다시 실행될 수 있으므로 결정 사항은 작업에 붙는 작은 저장값인 메모에 기록하며, 첫 번째 쓰기가 우선함
    • 배포 승인 예제는 Slack에서 받은 승인 결과를 저장해 재시작 후 다시 묻지 않음
  • 여러 확장이 같은 지점에 훅을 걸면 대화에서 확장을 선택한 순서대로 연결 실행함
    • beforeTool은 수정한 인자를 다음 훅에 전달하며, 첫 차단에서 멈춤
    • afterTool은 결과를 다음 훅으로 전달함
    • onYield는 실행을 계속하도록 결정한 첫 훅에서 멈춤
    • afterResponse 같은 관찰 훅은 모두 실행함
  • 훅에서 예외가 발생하면 이를 보고하고 체인을 계속 실행하지만, beforeTool의 예외는 도구 호출을 차단함
사용자 정의 작업과 소유권 트리
  • 내장 작업은 모델 요청, 도구 호출, 대화 압축을 담당하며, 확장의 사용자 정의 작업에도 같은 실행 장치를 제공함
    • 단계별 체크포인트, 재시작을 견디는 타이머, 다른 작업의 완료 대기를 지원함
  • 여러 카드로 결제 금액을 나누는 예제는 각 카드 결제를 동시에 실행하고, 하나가 거절되면 나머지를 중단해 환불하도록 구성함
    • 결제 작업 ID를 기반으로 한 멱등성 키로 재실행 시 중복 청구를 막음
    • failFast 대기 정책은 첫 결제 실패 시 다른 결제를 중단함
    • 중단 처리에서 각 결제 작업이 자신의 환불을 수행함
  • 작업과 대화는 하나의 소유권 트리를 형성함
    • 작업을 중단하면 그 작업이 소유한 항목도 아래에서 위로 중단해 각 작업이 자신의 부수 효과를 먼저 정리함
    • 소유한 작업이 모두 끝나야 부모 작업도 완료됨
    • 도구 호출이 소유하는 서브에이전트 대화에도 같은 규칙이 적용됨
  • 기본 작업은 포그라운드로 실행되며, 완료 전까지 대화는 유휴 상태가 되지 않음
    • 사용자가 Esc 등으로 대화를 중단하면 현재 작업과 그 하위 작업도 중단됨
  • 백그라운드 작업은 대화에 속하지만 현재 실행 중인 일에는 포함되지 않음
    • 작업이 계속 실행돼도 대화는 유휴 상태가 될 수 있고, 일반적인 대화 중단은 해당 작업과 하위 작업을 건드리지 않음
    • 시작한 턴보다 오래 살아야 하는 서브에이전트나 다음 날 실행할 알림에 적합함
    • 작업 자체를 중단하거나 대화를 { background: true }로 중단하면 멈춤
백그라운드 압축과 새 컨텍스트로 인계
  • 대화 압축도 하나의 작업이며, 대화를 계속 진행하면서 백그라운드에서 수행함
    • 컨텍스트가 모델 한도에 가까워지면 오래된 메시지를 요약하고 다음 턴 경계에 요약을 반영함
    • 다음 요청이 한도를 넘게 되는 경우에만 요약을 기다림
    • 제공자가 여전히 요청을 너무 길다고 거절하면 압축 후 한 번 재시도함
    • 별도 지침으로 언제든 수동 압축할 수 있으며, 원래 메시지는 저장소에 남음
  • 압축 시작 여유와 요청 대기 기준을 설정할 수 있음
    • 예제의 reserveTokens: 16384는 다음 요청이 요약을 기다리는 기준을 정함
    • backgroundTokens: 32768은 그 기준보다 얼마나 앞서 백그라운드 요약을 시작할지 정함
  • reset() 은 선택적인 인계 메모와 함께 새 컨텍스트를 시작하며, 도구도 control: { handoff } 반환으로 같은 동작을 요청할 수 있음
    • 이전 기록을 삭제하지 않으므로 별도 검색 도구가 인계 이전 메시지를 조회할 수 있음
    • 인계 후 실행할 메시지를 대기열에 넣으면 에이전트가 스스로에게 인계하고 나중에 과거 기록을 찾아보는 구성이 가능함
애플리케이션 상태의 영속성
  • 할 일 목록, 계획, 티켓, 대화의 샌드박스 같은 애플리케이션 상태는 타입이 있는 JSON 문서로 저장함
  • 문서는 대화 기록과 같은 저장소에 두고 동일한 원자적 커밋으로 변경하므로 상태와 그 상태를 만든 기록이 어긋나지 않게 함
  • 각 문서는 분기 시 초기값 정책을 지정함
    • 부모가 분기 지점에 가졌던 값, 부모의 현재 값, 새로운 값 중 선택할 수 있음
    • 할 일 목록 예제는 history: "rewindable"과 fork: "asOf"로 분기 지점의 목록을 이어받음
  • 시스템 프롬프트 섹션에서 매 요청 전에 문서를 읽고, UI에서는 커밋된 문서 상태를 구독할 수 있음
실행 중 코드 교체
  • 대화가 실행 중이어도 레지스트리를 변경할 수 있으며, 같은 이름으로 확장을 설치하면 한 단계로 교체됨
  • 이미 실행 중인 도구 호출은 시작할 때의 코드로 마무리하고, 다음 호출부터 새 코드를 사용함
  • 대화에는 확장과 도구의 이름만 저장하므로 재시작 후에는 새 프로세스가 설치한 구현을 사용함
여러 사용자와 클라이언트의 동시 참여
  • UI에 필요한 모든 정보가 커밋된 상태이므로 여러 클라이언트가 같은 대화에 접속할 수 있음
    • 최초 접속 시 대화 기록, 스트리밍 중인 응답, 실행 중인 도구와 출력, 대기 메시지, 에이전트 설정, 사용량을 받음
    • 이후에는 변경분만 받으며, 늦게 참여하거나 재접속한 클라이언트도 현재 상태에서 시작함
  • 어느 클라이언트든 실행 방향을 조정하거나 후속 메시지를 대기열에 넣을 수 있음
    • whenBusy: "steer"로 제출한 메시지는 현재 도구 호출이 끝난 뒤 진행 중인 작업에 합류함
  • 원격 클라이언트용 watch() 는 커밋별 정확한 연산을 소켓으로 전달하기 적합한 작은 형태로 제공함
    • watchEvents()는 커밋을 기존 코딩 에이전트에서 익숙한 이벤트로 변환하지만 전송량은 더 커짐
실험 상태, 예제와 개발 방향
  • Pi Durable은 실험 단계이며 API가 바뀔 수 있음. Pi 저장소의 packages/durable에서 코드를 확인할 수 있음
    • README에서 사용 방법을 안내함
    • 30개 이상의 예제에서 기능별 구현 사례를 볼 수 있음
    • 작은 코딩 에이전트는 Pi Durable 기반으로 구현됨
    • 휴가 계획 에이전트는 TUI 기반 데모임
  • 휴가 계획 에이전트는 TypeScript 약 1,300줄이며, 대부분은 Pi 코딩 에이전트의 구성요소를 재사용한 TUI 코드임
    • 서브에이전트가 세 검색을 병렬 작업으로 수행하는 동안 주 에이전트는 사용자와 대화함
    • 날씨와 박물관 검색이 끝나고 기차 검색이 진행 중일 때 프로세스를 종료한 뒤, 재시작하면 안전하게 재실행 가능한 기차 검색만 다시 수행함
    • 실행 중 서브에이전트의 방향을 조정하고, 주 대화에서 질문, 압축, 방향 조정을 계속할 수 있음
    • 완료 보고서는 메시지로 도착하고 주 에이전트가 이를 계획으로 바꿈
  • 자체 프로젝트에서는 @earendil-works/pi-durable, @earendil-works/pi-ai, @earendil-works/chord 패키지를 사용함
  • 향후 몇 주 동안 Slack 봇과 GitHub 이슈 분류 봇 등 내부 업무용으로 만드는 작은 에이전트 도구를 추가로 공개할 계획임
  • TypeScript는 초기 구현을 시작하기 가장 쉬운 선택이었으며, 현재 개발도 여기에 집중함. 향후 Rust나 어셈블러로의 이식을 배제하지는 않음
그냥 목록으로
원문 보기 ↗