본문으로 건너뛰기
5분 AI.
GITHUB TRENDING MONTHLY #1 딥다이브 · 2026-10-23 분석

paperclip 딥다이브
— AI 직원들의 업무·권한·비용을 지키는 회사 운영 서버

운영 플랫폼

빠른 판단

무엇
에이전트별 목표·업무·권한·예산을 연결해 실행·검토·비용·복구를 관리하는 셀프호스팅 운영 플랫폼
이럴 때
여러 AI 에이전트에 일을 나누고 승인·진행 상황·사용량을 공통 웹 화면에서 관리할 때
에이전트 하나가 코드를 잘 쓰는 것과 여러 에이전트가 함께 일을 끝내는 것은 다른 문제입니다. paperclip은 목표·조직도·업무·승인·예산을 연결하고, 서로 다른 에이전트의 실행을 관리하는 자체 호스팅 애플리케이션입니다. TypeScript 웹 서비스부터 PostgreSQL의 동시성 제어, Rust 실행기의 복구 설계까지 따라가며 “AI를 호출하는 코드”가 “운영할 수 있는 제품”이 되는 과정을 배웁니다. (paperclipai/paperclip · ⭐106,337 · 포크 17,680 · TypeScript · MIT · 2026-10-23 GitHub 기준)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

에이전트가 일을 한다면, 누군가는 일할 조건을 관리해야 한다

paperclip은 여러 AI 에이전트의 업무 배정·실행·검토·비용을 한곳에서 관리하는 운영 플랫폼입니다. 브라우저에서 회사를 만들고 목표와 프로젝트를 연결한 뒤, 에이전트에게 업무를 배정합니다. 서버는 실행 가능한지 확인하고 적절한 실행 환경으로 보내며, 결과와 사용량을 기록합니다.

독자가 가장 먼저 기억할 문장은 이것입니다. 에이전트의 추론 능력보다, 여러 실행이 함께 일할 때 지켜야 할 규칙을 설계하는 저장소입니다. 모델이 답변을 만드는 내부 알고리즘보다 “이 업무를 지금 시작해도 되는가”, “중지 뒤에 누가 정리하는가”라는 질문이 코드의 중심에 있습니다.

한 문장 비유

에이전트가 직원이라면, paperclip은 업무대장과 인사·회계·결재 시스템입니다

유능한 직원 열 명을 한 방에 모았다고 회사가 저절로 굴러가지는 않습니다. 같은 일을 두 명이 잡거나, 결재 없이 비용을 쓰거나, 퇴근한 직원의 업무가 계속 진행 중으로 남을 수 있습니다.

paperclip은 직원에게 일을 잘하는 법을 전부 가르치기보다 누가 어떤 일을 맡았고, 어떤 권한과 예산으로 실행하며, 어디까지 끝냈는지를 관리합니다. 이 비유에서 모델은 두뇌, Claude Code·Codex 같은 도구는 작업 장비, paperclip은 운영 체계에 가깝습니다.

용어
제어 평면 (control plane)
실제 작업을 수행하는 곳과, 작업의 배치·권한·상태를 관리하는 곳을 구분하는 말입니다. 공장의 기계가 제품을 만들고 관제실이 작업 지시와 가동 상태를 관리하듯, paperclip 서버는 에이전트 실행을 조율합니다. 실제 작업은 로컬 프로세스·SSH 원격 환경·샌드박스 등에서 수행될 수 있습니다.

1-1. 화면에서 보는 것과 서버가 책임지는 것

화면의 개념코드에서 확인할 대상배울 질문
회사와 에이전트companies·agents 테이블다른 회사의 데이터에 접근하지 못하게 하려면?
목표·프로젝트·업무issues의 goalId·projectId·parentId작은 작업에 상위 목적을 어떻게 연결할까?
에이전트 실행heartbeat_runs와 heartbeat 서비스배정한 업무와 실제 실행 기록은 왜 분리할까?
비용과 승인budget·approval 관련 서비스와 스키마금액과 권한이 바뀌면 진행 중 작업은 어떻게 할까?

회사·업무·실행을 분리해 두면 하나의 업무를 여러 차례 재시도하더라도 업무 자체를 복제할 필요가 없습니다. packages/db/src/schema/issues.ts와 heartbeat_runs.ts를 나란히 읽으면 이 차이가 드러납니다. 업무는 “끝내야 할 것”, 실행은 “그 일을 하려고 시도한 한 번”입니다.

학습 목표
작은 서비스에도 옮길 수 있는 설계가 많다

AI 제품을 만들지 않더라도 배울 것이 있습니다. 예약 서비스의 중복 접수 방지, 쇼핑몰의 재고 차감, 파일 업로드의 무결성 검사, 관리자 화면의 실시간 갱신에도 같은 원리가 쓰입니다.

2왜 주목받는가

모델 호출 다음에 생기는 운영 문제를 제품으로 묶었다

2-1. 터미널 여러 개로 관리하기 어려워지는 순간

에이전트 하나를 쓸 때는 대화창만 있어도 충분할 수 있습니다. 하지만 조사 담당, 구현 담당, 검토 담당이 동시에 움직이면 담당자·진행 상태·승인·비용을 사람이 따로 맞춰야 합니다. README는 이 문제를 조직도, 목표, 예산, 거버넌스라는 회사 운영 개념으로 설명합니다.

이 제품 구성이 관심을 모았을 가능성은 높습니다. 다만 트렌딩 순위만으로 유입의 원인을 입증할 수는 없습니다. 2026-10-23 GitHub 기준 Monthly #1, 스타 106,337개, 포크 17,680개는 관심의 규모를 보여 줍니다. 아래의 주목 요인은 README의 제품 방향과 구현된 기능을 바탕으로 한 해석입니다.

관심을 끌 만한 지점소스에서 연결되는 근거학습 가치
기존 에이전트를 가져와 연결server/src/adapters/registry.ts에 Claude·Codex·Cursor 등 등록외부 도구마다 다른 실행 방식을 계약으로 감싸기
업무의 중복 실행 방지issueService().checkout()의 조건부 갱신동시 요청에 안전한 상태 변경
비용을 운영 규칙에 포함server/src/services/budgets.ts의 정책·예약·중지 처리금액 표시와 지출 통제를 분리해 이해하기
자체 호스팅과 웹 관리 화면Express 서버·React UI·내장 PostgreSQL 초기화개발부터 배포까지 이어지는 제품 구성

이 저장소의 강점은 서로 다른 에이전트를 같은 업무·권한·비용 체계 안에서 운영하게 해 준다는 점입니다. 모델 성능이 같더라도 운영 규칙을 공유하면 사람이 결과를 추적하고 개입하기 쉬워집니다.

2-2. 비슷한 도구와 비교하면 어디에 놓이는가

비교 대상주된 출발점paperclip이 유리한 상황
Claude Code·Codex 같은 코딩 에이전트하나의 작업 환경에서 코딩과 도구 사용여러 에이전트의 담당 업무와 이력을 함께 관리할 때
LangGraph상태·노드·전이로 장기 실행 에이전트 흐름을 구현회사·업무·예산을 다루는 관리 애플리케이션부터 필요할 때
CrewAI의 공개 프레임워크에이전트 팀과 Flow를 코드로 구성서로 다른 실행 도구를 공통 운영 화면에 연결할 때
일반 업무 보드할 일·담당자·진행 상태를 사람이 관리업무 배정이 실제 에이전트 실행·세션·비용과 이어져야 할 때

비교 기준은 각 도구의 중심 추상화입니다. LangGraph 공식 개요 문서는 상태를 갖는 그래프와 영속 실행을, CrewAI 공식 소개는 에이전트 팀과 Flow를 설명합니다. 이들도 영속성·감시·사람의 개입을 지원하는 생태계가 있으므로, 특정 기능의 독점 여부나 성능 순위를 뜻하는 표는 아닙니다.

비유

LangGraph나 CrewAI로 작업자의 일하는 절차를 설계할 수 있다면, paperclip에서는 그 작업자가 속한 조직의 업무대장과 결재 흐름을 관리합니다. 실제 선택에서는 둘이 연결될 수도 있습니다. 비교할 것은 이름보다 내가 직접 만들어야 하는 관리 기능의 범위입니다.

2-3. 인기가 곧 쉬운 입문 난이도를 뜻하지는 않는다

공개 프레임워크 하나를 감싼 작은 예제가 아닙니다. 웹 화면, 인증, 데이터베이스, 원격 실행, 플러그인, 복구 로직까지 있습니다. TypeScript·TSX만 합쳐 약 260.8만 줄이며 테스트·스토리·생성 파일도 포함한 물리적 줄 수입니다. 초보자는 전체를 정독하기보다 4절의 한 가지 요청 경로부터 따라가는 편이 좋습니다.

3기술 스택 전체 지도

의존성 선언, 잠금 파일, 빌드 도구를 서로 구분해 읽는다

아래 버전은 커밋 412914c 기준입니다. package.json의 ^는 허용 범위이고, pnpm-lock.yaml의 값은 그 시점에 기록된 해석 결과입니다. 의존성 선언의 버전 범위와 실제 잠금 버전을 구분해야 같은 환경을 설명할 수 있습니다.

용어
모노레포와 워크스페이스
서버·화면·공통 타입 등 여러 패키지를 하나의 저장소에서 관리하는 구성이 모노레포입니다. pnpm 워크스페이스는 패키지를 함께 설치하고 서로 연결합니다. workspace:*는 인터넷의 임의 버전 대신 같은 워크스페이스의 패키지를 참조한다는 표시입니다.

3-1. 백엔드와 데이터 계층

기술실제 선언 / 잠금 값담당 역할과 근거 파일
Node.js>=24.11.0서버·CLI 런타임. 루트 및 각 패키지의 engines
Express^5.1.0 / 5.2.1HTTP API·미들웨어. server/package.json
Drizzle ORM^0.45.2 / 0.45.2TypeScript 스키마·SQL 쿼리. packages/db/package.json
postgres^3.4.9 / 3.4.9PostgreSQL 접속 드라이버. packages/db/src/client.ts
embedded-postgres^18.1.0-beta.16 / 동일 버전로컬 PostgreSQL 프로세스 관리. 서버와 DB 패키지
Zod^4.4.3 / 4.4.3입력값을 실행 시 검증. packages/shared/package.json
Better Auth1.7.2인증 세션 기반. server/package.json
ws / Pino^8.22.0 / ^10.0.0WebSocket 통신 / 구조화 로그. 서버 의존성 선언
AI SDK / MCP SDKai: 7.0.130 / @modelcontextprotocol/sdk: ^1.31.0모델·도구 연결에 쓰는 서버 의존성

packages/db/src/client.ts는 drizzle-orm/postgres-js와 postgres를 가져옵니다. server/src/index.ts는 연결 문자열이 있으면 외부 PostgreSQL을 쓰고, 없으면 embedded-postgres를 동적으로 불러옵니다. 내장 모드도 실제 PostgreSQL을 관리하는 구성이지 브라우저 안에서 동작하는 DB라는 뜻이 아닙니다. 단, authenticated·public 배포는 외부 PostgreSQL 연결 문자열이 필수입니다.

문서와 구현의 차이
AGENTS.md의 PGlite 설명은 현재 초기화 코드와 맞지 않는다

루트 AGENTS.md의 개발 설정에는 “PGlite”와 data/pglite가 남아 있습니다. 반면 README의 빠른 시작은 내장 PostgreSQL을 설명하며, 의존성과 server/src/index.ts도 이를 뒷받침합니다. DB의 종류와 데이터 위치를 판단할 때는 현재 초기화 코드와 설정 경로를 함께 확인해야 합니다.

3-2. 프론트엔드

기술버전 선언어디에 쓰이는가
React / React DOM^19.2.8 / 각각 잠금 19.2.8관리 화면과 상태에 따른 렌더링
React Routerreact-router-dom: ^7.18.4회사·업무·에이전트 화면의 이동
TanStack Query^5.102.8서버 데이터의 캐시·재조회·부분 갱신
Vite^8.2.2 / 잠금 8.2.2개발 서버·번들. ui/vite.config.ts
Tailwind CSS^4.3.3스타일과 Vite 플러그인. ui/package.json
Base UI / Radix UI^1.7.0 / radix-ui: ^1.6.7접근성을 고려한 UI 구성 요소
Lexical / Yjs0.48.0 / 13.6.32편집기·공유 문서 관련 기반
Storybook10.6.0화면 상태별 예제. ui/storybook/

프론트 버전의 근거는 ui/package.json입니다. 같은 저장소의 Runner 개발 화면은 별도 package.json에서 다른 Vite 범위를 선언하므로, 특정 버전을 “저장소 전체가 공통으로 사용한다”고 일반화하면 안 됩니다.

3-3. 인프라·빌드·검증

계층설정값읽을 포인트
패키지 관리pnpm@9.15.4루트 packageManager로 도구 버전 고정
정적 타입 검사typescript: ^7.0.2, 잠금 7.0.2루트·서버·UI 패키지의 타입 계약 확인
테스트vitest: ^5.0.3, 루트 @playwright/test: ^1.62.1함수·서비스 검증과 브라우저 검증을 분리
Rust 실행기rust-toolchain.toml: 1.97.1, edition 2021packages/paperclip-runner/runner/Cargo.toml
Rust 의존성serde: 1.0, tokio: 1.50, tungstenite: 0.28Cargo 워크스페이스 선언 범위. 직렬화·비동기·WebSocket
컨테이너node:24-trixie-slim, PORT=3100루트 Dockerfile의 다단계 빌드
CINode 24, pnpm 9.15.4.github/workflows/pr-trusted.yml의 정책·타입·테스트·빌드 작업

pnpm-workspace.yaml은 서버·UI·CLI와 여러 패키지를 묶으면서 packages/plugins/sandbox-providers/**는 제외합니다. 해당 플러그인의 외부 의존성을 독립적으로 설치하려는 경계입니다. 또한 postgres·embedded-postgres 등에 패치를 적용합니다. 버전 숫자만 같아도 패치가 다르면 코드가 같지 않을 수 있습니다.

CI는 pr.yml에서 재사용 워크플로를 호출합니다. pr-trusted.yml의 정책 작업은 수동 잠금 파일 변경을 제한하고 의존성 해석을 검사합니다. 설치 액션은 고정 잠금 설치가 실패하면 잠금을 다시 해석하는 경로도 갖습니다. 따라서 잠금 파일은 중요한 근거이지만, 모든 빌드가 아무 조건 없이 동일한 의존성으로 끝난다는 증거는 아닙니다.

4아키텍처 심화 분석

업무 하나가 배정되고, 실행되고, 화면에 반영되는 길

4-1. 전체 시스템 구조

[사람: 브라우저] [에이전트 / CLI / MCP] | HTTP + WebSocket | API / 도구 호출 v v +----------------------------------------------------------------+ | Express 제어 서버 | | app.ts / routes/ / 인증·회사 접근 검사 | | | | | +--> issues 서비스: 업무·담당자·실행 잠금 | | +--> budgets / approvals: 예산·승인 | | +--> heartbeat: 대기열 - 준비 - 실행 - 결과 정리 | | | | | +-----------+------------------+ | | v v | | native-runtime legacy adapter | | Rust Runner 연결 execute(ctx) | +-------------|------------------------------|-------------------+ | | v v [로컬 / SSH / 샌드박스의 에이전트 실행 환경] | +--> 모델 제공자 / 파일 / 외부 도구 [서버] <--> [Drizzle + PostgreSQL: 업무·실행·비용·감사 기록] | +--> [로컬 파일 또는 설정한 스토리지: 로그·첨부·산출물] | +--> [회사별 실시간 이벤트] --> [React Query 캐시와 화면]

이 그림의 핵심 경계는 업무를 관리하는 서버와 실제로 도구를 실행하는 환경 사이입니다. 같은 컴퓨터에 둘 다 있을 수도 있고 원격으로 떨어져 있을 수도 있습니다. 경계가 나뉘면 네트워크 단절을 프로세스 종료로 오해하지 않는 설계가 필요해집니다.

서버의 시작점은 server/src/index.ts입니다. 설정과 DB를 준비하고 server/src/app.ts의 createApp()으로 HTTP 애플리케이션을 구성합니다. UI가 같은 주소에서 보이더라도 API·업무 서비스·실행 환경은 서로 다른 책임을 갖습니다.

4-2. 패턴 ① 조건부 갱신으로 업무 소유권 확보

POST /api/issues/:id/checkout은 에이전트가 업무를 가져가는 입구입니다. server/src/routes/issues.ts는 요청 검증, 접근 가능한 업무 조회, 프로젝트 중지 여부, 에이전트 본인의 ID인지 등을 확인한 뒤 svc.checkout()을 호출합니다.

// packages/shared/src/validators/issue.ts의 실제 입력 계약
export const checkoutIssueSchema = z.object({
  agentId: z.string().guid(),
  expectedStatuses: z.array(z.enum(ISSUE_STATUSES)).nonempty(),
});

서비스 구현은 server/src/services/issues.ts의 issueService(db)가 반환하는 checkout 메서드입니다. 회사 내 배정 가능 여부, 업무 트리의 중지 조건, 선행 업무의 해결 여부를 검사합니다. 그다음 예상 상태·담당자·실행 잠금 조건이 모두 맞는 행만 UPDATE합니다.

-- 실제 Drizzle 조건을 단순화해 풀어 쓴 학습용 SQL
UPDATE issues
SET assignee_agent_id = :agent_id,
    checkout_run_id = :run_id,
    execution_run_id = :run_id,
    status = 'in_progress'
WHERE id = :issue_id
  AND status IN (:expected_statuses)
  AND (assignee_agent_id IS NULL OR :same_run_assignee)
  AND (execution_run_id IS NULL OR execution_run_id = :run_id)
RETURNING *;

“먼저 조회했을 때 비어 있었다”는 사실만 믿으면 두 요청이 모두 성공할 수 있습니다. 이 코드는 갱신하는 순간에도 조건을 검사합니다. 다른 실행이 먼저 소유권을 잡으면 후속 갱신의 조건이 맞지 않습니다. 갱신 결과가 없을 때는 현재 상태를 다시 읽어 같은 실행의 재요청인지, 복구 가능한 오래된 잠금인지 등을 판별합니다.

용어
원자적 변경과 멱등성
원자적이라는 말은 하나의 변경이 중간 상태 없이 적용된다는 뜻입니다. 멱등성은 같은 요청을 다시 처리해도 추가 효과가 생기지 않는 성질입니다. 조건부 UPDATE는 경쟁을 막는 도구이고, 같은 실행의 재요청을 허용할지는 별도의 서비스 규칙입니다. 두 개념을 구분해야 합니다.

복구·재요청 조건까지 해결되지 않으면 conflict("Issue checkout conflict", ...)로 끝납니다. 또한 assertCheckoutOwner()는 이후 변경에서도 소유권을 검사합니다. 잠금을 처음 잡았다는 이유로 모든 후속 행동을 영구 허용하지 않는 것이 중요합니다.

비유

호텔 예약 화면에서 빈 객실을 본 것과 예약 확정은 다릅니다. 결제 직전에 객실이 아직 비어 있는지 확인하며 예약 상태를 바꿔야 합니다. expectedStatuses는 “내가 봤던 상태일 때만 처리해 달라”는 조건표입니다.

4-3. 패턴 ② 실행 조율과 제공자 구현을 분리

server/src/services/heartbeat.ts의 heartbeatService()는 실행 생명주기를 조립합니다. createHeartbeatQueue()가 대기열을, executeRunAttempt()가 실행 시도를, createHeartbeatRunCompletion()의 completeRun()이 완료 처리를 맡습니다.

단계주요 파일해당 단계의 질문
대기열과 시작server/src/services/heartbeat/queue.ts이미 진행 중인 작업과 겹치는가? 새 실행이 필요한가?
실행 문맥·세션 준비run-preparation.ts·session-preparation.ts어떤 업무·도구·이전 대화를 전달할까?
환경·런타임 선택runtime-selection.ts·workspace-preparation.ts어디서 어떤 방식으로 실행할까?
실행runtime-execution.ts준비된 실행을 어느 백엔드에 넘길까?
정리와 복구run-completion.ts·recovery.ts결과·상태·리소스를 어떻게 정리하고 이어 갈까?

executeHeartbeatRuntime()에는 native 실행과 legacy adapter 실행 경로가 함께 있습니다. native 경로는 executePaperclipNativeSession()에 위임하고, legacy 경로는 adapter.execute(...)를 호출합니다. 현재 코드를 옛날 어댑터 설명 하나로만 이해하면 Rust Runner 경로를 놓칩니다.

// packages/adapter-utils/src/types.ts의 계약 일부
export interface ServerAdapterModule {
  type: string;
  execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
  testEnvironment(ctx: AdapterEnvironmentTestContext): Promise<AdapterEnvironmentTestResult>;
  // 세션·모델·스킬 등 선택적 기능은 별도 필드로 선언
}

이것은 전략 패턴을 읽는 좋은 예입니다. 호출자는 공통 계약을 사용하고, 제공자별 명령과 결과 해석은 구현체가 맡습니다. AdapterExecutionResult는 종료 코드뿐 아니라 usage, costStatus, sessionParams 등도 표현합니다.

server/src/adapters/registry.ts는 구현을 등록하고 선택합니다. getServerAdapter()는 찾지 못할 때 process 어댑터로 대체하지만, requireServerAdapter()는 알 수 없는 유형을 오류로 처리합니다. 같은 레지스트리라도 호출자가 요구하는 실패 정책에 따라 함수가 다릅니다.

4-4. 패턴 ③ 영속 상태로 실행과 복구를 조율

실행 결과를 받았다는 사실과 프로세스 종료·비용 정산·파일 정리가 끝났다는 사실은 서로 다릅니다. heartbeat_runs에는 실행 상태 외에도 정산 대기 여부, 세션 ID, 로그 참조, 재시도 연결, 컨트롤러 소유권 관련 필드가 있습니다. 이런 구분이 있어야 재시작 뒤에 남은 의무를 판단할 수 있습니다.

Rust 구현은 packages/paperclip-runner/runner/crates/runner-core/src/durable/에 있습니다. 그 안의 state.rs에서 Command는 command_id와 controller_seq를 갖고, validate()는 스키마 버전·식별자·순번·페이로드 형태를 검사합니다.

// durable/state.rs의 명령 필드 일부
pub struct Command {
    pub schema: String,
    pub command_id: String,
    pub controller_seq: u64,
    // 명령 유형·시각·사전 조건·페이로드가 이어짐
}

같은 파일의 save()는 상태를 직렬화하고 임시 파일에 쓴 다음 sync_all(), fs::rename()을 거쳐 상태 파일을 교체합니다. Unix에서는 상위 디렉토리도 동기화합니다. 파일에 JSON을 썼다는 사실과 장애 후에도 복구할 수 있게 저장했다는 사실의 차이를 보여 줍니다.

서버의 예약 작업도 메모리 타이머 하나에 모든 책임을 맡기지 않습니다. createWorkScheduler()는 이름별 기한을 Map에 넣고 가장 가까운 기한에 타이머를 맞춥니다. createDeliveryWorkCoordinator()는 중복 실행을 조정하고 실패 후 재시도와 DB 작업 신호를 연결합니다. 다만 이 스케줄러 자체는 프로세스 메모리이므로, 재시작 복구에는 영속 데이터에서 할 일을 다시 구성하는 경로가 필요합니다.

예산 검사도 실행 권한의 일부다

server/src/services/budgets.ts의 budgetPoliciesForRun()은 회사·에이전트·프로젝트에 해당하는 정책을 고릅니다. recheckAdmittedRunBudgetReadOnly()는 실행이 보유한 예약과 현재 지출을 다시 검사합니다. 미정산 실행이나 가격을 확정할 수 없는 사용량도 정책에 따라 실행을 막을 수 있습니다.

이 재검사는 이미 승인된 실행의 인계 단계에 붙는 읽기 전용 보완 검사입니다. 예산 초과 시 중지 전달과 정산은 다른 경로의 책임입니다. 따라서 “예산 상한이 있으니 외부 제공자의 청구액도 언제나 한 푼도 넘지 않는다”고 해석해서는 안 됩니다.

4-5. 패턴 ④ 알림으로 화면을 갱신하되 원본 데이터는 다시 확인

server/src/services/live-events.ts의 publishLiveEvent()는 회사별 EventEmitter 알림을 발행합니다. server/src/realtime/live-events-ws.ts가 /api/companies/:id/events/ws 연결을 처리하며 접근 권한을 검사합니다. 회사 ID가 URL에 있다는 사실만으로 구독 권한이 생기지는 않습니다.

클라이언트의 ui/src/context/LiveUpdatesProvider.tsx는 소켓을 공유합니다. applyRunLifecycleToCompanyLiveRuns()는 기존 실행의 상태를 캐시에 반영하고, 종료된 실행을 활성 목록에서 제거합니다. 반면 필요한 데이터가 부족한 경우에는 다시 조회합니다.

상황현재 코드의 대응이유
기존 실행의 상태 변경setQueryData()로 캐시를 부분 갱신모든 이벤트마다 전체 데이터를 받지 않기 위해
첫 연결·재연결활성 쿼리를 무효화해 재조회연결 전에 놓친 이벤트가 있을 수 있기 때문에
연결 실패1·2·4·8초 뒤 재시도, 이후 최대 15초 간격과도한 재접속을 줄이기 위해
실시간 연결 불가15초 간격으로 활성 쿼리 갱신화면이 영구히 오래된 상태로 남지 않게 하기 위해
서로 다른 보장
UI 알림을 실행기의 영속 메시지처럼 생각하면 안 된다

이 UI 이벤트 경로는 프로세스 내부 알림이며 놓친 이벤트를 재생하지 않습니다. 클라이언트도 재연결 시 이를 전제로 데이터를 다시 조회합니다. Rust Runner의 영속 상태·명령 처리와는 목적이 다릅니다. 이 알림 코드만 보고 다중 서버 간 방송이나 모든 이벤트의 보존을 가정할 수 없습니다.

5디렉토리 구조 해부

폴더 이름보다 경계를 넘는 데이터의 흐름을 찾는다
paperclip/ |-- package.json / pnpm-workspace.yaml / pnpm-lock.yaml |-- server/ | `-- src/ | |-- index.ts / app.ts 서버 시작과 HTTP 앱 구성 | |-- routes/ 요청·응답과 접근 검사 | |-- services/ 업무 규칙과 실행 조율 | | |-- heartbeat.ts | | |-- heartbeat/ 실행 단계별 모듈 | | `-- native-runtime/ Rust Runner와의 연결 | |-- modules/ 기능별 소유 모듈 | `-- realtime/ 실시간 연결 |-- ui/ | |-- src/api/ 서버 API 호출 | |-- src/context/ 회사·실시간 갱신 등 공유 상태 | |-- src/pages/ 페이지 | |-- src/components/ UI 구성 요소 | `-- storybook/ 화면 예제·데이터 |-- packages/ | |-- db/src/schema/ 테이블 정의 | |-- db/src/migrations/ DB 변경 이력 | |-- shared/src/ 타입·상수·검증기 | |-- adapters/ 제공자별 연결 구현 | |-- adapter-utils/ 공통 실행 계약과 지원 코드 | |-- paperclip-runner/ | | |-- src/ TypeScript SDK·프로토콜 지원 | | |-- protocol/ 프로토콜 계약 | | `-- runner/crates/runner-core/src/ Rust 실행기 | |-- plugins/ SDK·예제·샌드박스 제공자 | |-- mcp-server/ MCP 패키지 | |-- skills-catalog/ 앱에 포함되는 스킬 목록 | `-- teams-catalog/ 팀 구성 자료 |-- cli/ paperclipai 명령행 도구 |-- skills/ 운영용 에이전트 지침 |-- tests/ 통합·브라우저·실행기 검증 등 |-- scripts/ 개발·빌드·검증 자동화 |-- doc/ / docs/ / maps/ / rules/ 설계·문서·기능 지도·기여 규칙 `-- Dockerfile / .github/workflows/ 이미지·CI·릴리스

5-1. 처음 읽을 때의 추천 순서

  1. packages/db/src/schema/issues.ts: 업무가 어떤 데이터를 갖는지 먼저 봅니다.
  2. packages/shared/src/validators/issue.ts: 외부 요청에서 허용하는 형태를 봅니다.
  3. server/src/routes/issues.ts: /issues/:id/checkout을 찾아 입구를 봅니다.
  4. server/src/services/issues.ts: checkout: async를 찾아 실제 변경 조건을 봅니다.
  5. ui/src/context/LiveUpdatesProvider.tsx: 변경이 화면에 어떻게 반영되는지 봅니다.

서비스 파일은 매우 크므로 첫 줄부터 읽으면 맥락을 잃기 쉽습니다. 함수 이름으로 이동하고, 인자와 반환값을 기록한 뒤, 필요한 호출만 확장하세요. 폴더 전체를 이해하는 것보다 “업무 하나의 소유권이 바뀌는 경로”를 설명할 수 있는 것이 먼저입니다.

5-2. 직접 집계한 코드와 지원 파일의 규모

언어·확장자파일 수물리적 줄 수
TypeScript .ts6,4302,038,371
TSX .tsx1,886569,131
Rust .rs7080,775
JavaScript .mjs/.cjs/.js51586,776
SQL .sql38215,712
CSS .css2512,942
Shell .sh5812,328
Python .py / Go .go26 / 36,411 / 98
위 확장자 합계9,3952,822,544

커밋 412914c의 Git 추적 파일에서 위 확장자의 줄바꿈을 합산한 값입니다. 빈 줄·주석·테스트·스토리·생성 코드가 포함되며, Markdown 등 문서 형식·JSON·잠금 파일은 제외했습니다. 의존성·빌드·캐시 디렉토리와 심볼릭 링크도 제외했습니다. 순수 구현량이나 기능 품질 점수로 읽으면 안 됩니다.

같은 확장자 기준으로 server/ 약 122만 줄, ui/ 약 67만 줄, packages/ 약 71만 줄, cli/ 약 4.5만 줄입니다. packages/db/src/migrations/에는 SQL 파일이 373개 있습니다. 이 숫자는 스키마 변화의 규모를 가늠하게 해 주지만, 이 파일 집계만으로 전체 개발 기간의 커밋 수나 기여 비중까지 알 수는 없습니다.

6학습 포인트 (기술별)

문법을 아는 단계에서 실패 조건을 설계하는 단계로

6-1. TypeScript + Zod: 타입과 입력 검증의 경계

TypeScript 타입은 개발 중 실수를 줄여 주지만 인터넷에서 들어온 JSON을 자동으로 검사하지 않습니다. server/src/middleware/validate.ts의 validate(schema)는 schema.parse(req.body)를 호출해 실제 값을 검사하고 다음 미들웨어로 넘깁니다.

// packages/shared/src/validators/issue.ts의 실제 타입 추론
export type CheckoutIssue = z.infer<typeof checkoutIssueSchema>;

// 읽기 실험: 아래 두 JSON을 스키마와 대조해 보세요.
{ "agentId": "올바른 UUID가 아님", "expectedStatuses": ["todo"] }
{ "agentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "expectedStatuses": [] }

배울 것: 하나의 스키마에서 검증과 타입을 함께 정의하는 방식입니다. 작은 실습: 위 두 요청이 각각 어느 조건에서 실패하는지 적고, “올바른 UUID와 비어 있지 않은 상태 배열”로 고쳐 보세요. 그다음 입력 형태가 맞아도 권한과 업무 상태 검사는 따로 필요하다는 점까지 설명해 봅니다.

6-2. PostgreSQL + Drizzle: SQL을 숨기지 않는 ORM 읽기

pgTable()은 테이블을 TypeScript로 표현하고, and()·eq()·isNull()은 SQL 조건을 구성합니다. ORM을 사용해도 조건부 UPDATE, 외래 키, 인덱스, 트랜잭션의 의미를 알아야 합니다. issues.ts에는 companyId와 상태를 묶은 인덱스도 있습니다.

배울 것: 데이터가 같은 회사에 속한다는 관계와, 요청자가 그 회사에 접근할 수 있다는 권한은 별도라는 점입니다. 작은 실습: 업무 목록을 “회사 + 담당자 + 상태”로 찾는다고 가정하고, 스키마의 assigneeStatusIdx가 어떤 조회를 돕는지 설명해 보세요.

용어
인덱스 (index)
데이터를 빨리 찾도록 따로 유지하는 찾아보기입니다. 복합 인덱스는 여러 열을 일정 순서로 묶습니다. 조회를 빠르게 할 수 있지만 변경할 때 유지 비용과 저장 공간이 듭니다. 접근 권한을 대신 검사해 주는 기능은 아닙니다.

6-3. React + TanStack Query: 화면 상태와 서버 상태

창이 열렸는지 같은 UI 상태와, 현재 업무가 진행 중인지 같은 서버 상태는 다릅니다. 후자는 다른 에이전트가 바꿀 수 있습니다. 이때 화면 안의 변수 하나만 바꾸면 다른 목록이나 상세 화면이 오래된 값을 갖게 됩니다.

배울 것: LiveUpdatesProvider.tsx에서 setQueryData()와 invalidateQueries()의 역할을 비교하세요. 전자는 아는 값을 즉시 반영하고, 후자는 필요한 데이터를 서버에서 다시 확인하도록 합니다. 작은 실습: 종료 이벤트를 두 번 받았을 때 활성 실행 목록에서 같은 실행을 제거해도 괜찮은 이유를 써 봅니다.

6-4. 어댑터 계약: “성공”보다 풍부한 결과 모델

packages/adapter-utils/src/types.ts의 AdapterExecutionResult에는 usageBasis가 per_run인지 session_cumulative인지 나타내는 필드가 있습니다. 세션을 재사용했다고 사용량까지 누적 값이라고 추정해서는 안 된다는 주석도 있습니다.

비유

자동차 계기판의 14,000km와 이번 여행의 4,000km는 다릅니다. 이전 계기판이 10,000km였다면 누적 값끼리 빼야 여행 거리가 나옵니다. 학습 예시로 토큰 누적값이 10,000에서 14,000으로 늘었다면 이번 사용량은 4,000입니다. 보고 방식이 다르면 같은 숫자의 뜻도 달라집니다.

배울 것: 외부 도구의 결과를 내부 계약으로 바꿀 때 정보의 기준도 함께 전달해야 합니다. 작은 실습: costStatus의 reported·estimated·unpriced를 한국어로 설명해 보세요. 가격을 알 수 없는 결과를 비용 0으로 취급하면 어떤 문제가 생길지도 적습니다.

6-5. Rust + 파일 저장: 안전하게 남기는 상태

packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs에서 Command::validate()와 save()를 읽습니다. Rust 문법을 모두 몰라도 Result가 실패를 표현하고, ?가 오류를 호출자에게 전달한다는 정도부터 시작할 수 있습니다.

배울 것: 상태 저장에 크기 제한·임시 파일·동기화·교체가 필요한 이유입니다. 작은 실습: “임시 파일을 쓰다가 종료”, “교체 직전 종료”, “교체 뒤 종료”를 나누고, 각 경우에 이전 파일과 새 파일 중 무엇을 읽을 수 있어야 하는지 표로 정리하세요. 실제 파일시스템의 보장은 별도 검증 대상입니다.

6-6. 원격 파일 전달: 경로보다 소유자와 내용을 확인

server/src/services/native-runtime/native-runner-file-handoff.ts의 readVerifiedWorkspaceFile()는 원격 실행이면 readRemoteTaskFile 또는 readRemoteWorkspaceFile에 읽기를 위임합니다. 받은 바이트 수와 SHA-256도 확인합니다.

로컬 경로에서는 작업 경계 밖 이동과 심볼릭 링크를 검사합니다. 배울 것: 원격 기기의 파일을 서버의 같은 문자열 경로에서 찾으면 안 된다는 점입니다. 작은 실습: native-runner-file-handoff.test.ts에서 원격 읽기와 해시 불일치 사례를 찾아, 경로 검사와 내용 검사가 각각 방어하는 실패를 구분합니다.

6-7. 테스트와 CI: 검증이 증명하는 범위를 읽기

server/src/__tests__/issues-checkout-wakeup.test.ts는 활성 실행 중인 에이전트의 자기 checkout이 추가 깨우기를 만들지 않는 규칙을 검사합니다. 이 테스트 하나가 DB의 모든 동시성까지 증명하지는 않습니다. 반면 budgets-service.test.ts는 내장 PostgreSQL을 이용하는 테스트 경로를 갖고 환경이 지원되지 않으면 건너뜁니다.

테스트를 읽을 때는 통과 여부와 함께 실제로 어떤 경계까지 실행했는지를 확인해야 합니다. 작은 실습: 함수 테스트·실제 DB 테스트·브라우저 테스트가 각각 발견할 수 있는 버그를 하나씩 적어 보세요. CI 파일을 읽으며 해당 검사가 어느 작업에서 실행되는지 연결하면 검증의 전체 지도가 생깁니다.

7하드웨어 / 시스템 요구사항

제어 서버, 소스 빌드, 에이전트 실행의 요구사항을 나눈다
항목확인할 조건근거와 적용 범위
Node.js24.11.0 이상루트 package.json의 실행 환경 선언
pnpm9.15.4packageManager 고정값. README는 9.15 이상으로 안내
Rust1.97.1 툴체인네이티브 실행기를 소스 빌드하는 경우. 패키지의 rust-toolchain.toml
네이티브 빌드 도구C 컴파일러·개발 라이브러리 등Docker의 Rust 단계는 gcc·libc6-dev·pkg-config 설치
데이터베이스내장 PostgreSQL 또는 외부 PostgreSQLDATABASE_URL 또는 설정 파일의 연결 문자열에 따른 초기화 분기
기본 포트웹/API 3100, 내장 DB 54329server/src/config.ts. 설정·사용 중 포트에 따라 달라질 수 있음
Linux 배포 바이너리x64·ARM64, glibc 2.39 이상Runner README의 배포 기준. 모든 배포판 지원을 뜻하지 않음
macOS 배포 바이너리ARM64·x64Runner README 기준. 아키텍처별 검증 범위에 차이가 있음
Windows·Alpine/muslnative 실행기 검증은 미완료로 문서화웹 UI 접속 가능 여부와 실행 호스트의 지원 여부는 별개
GPU외부 모델 API를 쓰는 제어 서버 구성에서는 필수 아님로컬 모델 추론을 추가하면 그 모델의 요구사항이 별도로 필요
CPU·메모리·저장 공간README에 최소 수치 미기재동시 실행 수·빌드·로그·작업 사본 크기에 크게 좌우됨

소스 전체 빌드까지 공부하려는 개인 환경은 4코어·RAM 16GB·여유 디스크 30GB 이상을 출발점으로 생각할 수 있습니다. 이것은 필자의 학습 환경 권장 추정이며 공식 최소 사양이나 측정 결과가 아닙니다. Rust 빌드 캐시와 에이전트별 저장소 사본이 쌓이면 이보다 더 필요할 수 있습니다.

필요한 자원은 에이전트 수보다 동시에 살아 있는 실행·프로세스·작업 사본의 수에 더 직접적으로 영향을 받습니다. 서버 메모리만 보지 말고 하위 프로세스, DB, 작업 디렉토리, 로그 저장량까지 함께 살펴야 합니다.

7-1. 기본 저장 경로와 운영 시 확인할 것

packages/shared/src/home-paths.ts는 기본 홈을 ~/.paperclip, 인스턴스 ID를 default로 정합니다. 기본 인스턴스 루트는 ~/.paperclip/instances/default이며 PAPERCLIP_HOME과 PAPERCLIP_INSTANCE_ID로 바꿀 수 있습니다.

인스턴스 루트 아래 경로용도
config.json / db/설정 / 내장 PostgreSQL 데이터
logs/로그
data/storage/ / data/backups/기본 파일 저장소 / 백업 디렉토리
secrets/master.key기본 비밀정보 키 경로. DB만 보관하는 것과 별도로 복구 계획이 필요

외부 DB·객체 저장소·원격 작업 환경을 사용하면 데이터가 여러 곳에 나뉩니다. 따라서 기본 폴더 하나의 복사만으로 모든 구성의 완전한 복구가 된다고 가정하면 안 됩니다. 코드의 경로 기본값과 실제 배포 설정을 함께 보아야 합니다.

7-2. 실행 모드와 외부 연결

서버 설정의 기본 호스트는 127.0.0.1입니다. Docker 이미지는 HOST=0.0.0.0과 PAPERCLIP_DEPLOYMENT_MODE=authenticated, PAPERCLIP_DEPLOYMENT_EXPOSURE=private를 선언합니다. 로컬 실행과 컨테이너의 기본값을 혼동하지 마세요.

자체 호스팅의 범위
내 서버에 띄워도 모든 통신이 로컬로 끝나는 것은 아니다

선택한 모델·연결 도구·원격 실행 환경은 외부 서비스와 통신할 수 있습니다. README와 서버 설정상 제품 사용 텔레메트리도 기본 활성화입니다. README는 PAPERCLIP_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1 등의 비활성화 방법을 안내합니다.

이 경로는 운영자가 OTLP 주소를 지정해야 활성화되는 OpenTelemetry 추적과 구분해야 합니다. “데이터를 직접 보관한다”는 말과 “어떤 외부 요청도 없다”는 말은 같은 뜻이 아닙니다.

README의 소스 개발 시작 명령은 다음과 같습니다. 전체 빌드에는 Rust 등 추가 요구사항이 연결되며, 실제 에이전트 실행에는 해당 제공자의 인증과 실행 환경 준비가 필요합니다.

pnpm install
pnpm dev
# README 기준 UI와 API: http://localhost:3100
# 로컬 구성에서 외부 DB 연결 문자열이 없으면 내장 PostgreSQL을 준비

8직접 해볼 수 있는 실습 과제

읽기 과제에서 시작해, 격리된 작은 실험으로 검증한다

앞의 두 과제는 애플리케이션을 띄우지 않고 시작할 수 있습니다. 실행 실험은 별도의 학습용 환경에서 가짜 에이전트와 연습용 데이터로 진행하세요. 좋은 실습의 완료 기준은 코드를 바꾼 양보다 실패 상황의 결과를 설명하고 확인할 수 있는가에 있습니다.

과제 1입문 · 30~60분

checkout 요청 한 건의 지도를 그리기

입력 스키마 → 라우트 → 서비스 → DB 열을 연결합니다. 각 단계 옆에 “잘못된 입력”, “다른 에이전트 ID”, “예상과 다른 상태”, “다른 실행의 잠금”을 적으세요.

rg -n 'checkoutIssueSchema' packages/shared/src/validators/issue.ts
rg -n '/issues/:id/checkout' server/src/routes/issues.ts
rg -n 'checkout: async|Issue checkout conflict' server/src/services/issues.ts

완료 기준: 입력 형식 오류, 권한 거부, 상태 충돌을 서로 다른 단계로 설명한 한 장짜리 지도를 만듭니다. 정상 요청의 JSON 예시도 하나 작성합니다.

과제 2입문 · 1시간

데이터 모델에서 업무와 실행을 분리해 보기

issues.ts와 heartbeat_runs.ts에서 업무 식별자, 담당자, 상태, 실행 ID, 세션 ID, 비용 정산 필드를 찾아 표로 옮깁니다. “업무 하나를 두 번 시도했다”는 예시 데이터를 종이에 만드세요.

완료 기준: 업무 1개와 실행 2개가 연결된 관계도를 그립니다. 첫 실행이 실패해도 업무 제목을 복제할 필요가 없는 이유, 실행 종료와 정산 완료를 나눠야 하는 이유를 각각 두 문장으로 씁니다.

과제 3중급 · 2~3시간

조건부 UPDATE의 경쟁을 작은 DB에서 재현하기

격리된 연습용 PostgreSQL에 ID·상태·실행 ID만 가진 작은 테이블과 행 하나를 만듭니다. 두 연결에서 같은 행을 서로 다른 실행 ID로 가져가 보세요. 먼저 조회 뒤 무조건 갱신하는 방식과, 갱신 조건에 “실행 ID가 비어 있음”을 넣은 방식을 비교합니다.

완료 기준: 두 구현의 실행 순서표와 결과를 남깁니다. 이 작은 실험이 전체 checkout()의 권한·복구 정책까지 증명하지는 않는다는 범위도 명시합니다.

과제 4중급 · 반나절

외부 모델 없이 실행 계약을 설계하기

독립된 TypeScript 연습 프로젝트에서 성공·실패·타임아웃을 반환하는 가짜 실행기를 만듭니다. 아래는 실제 어댑터보다 작은 교육용 계약입니다. 특정 모델의 SDK를 조율 코드에 넣지 않는 데 초점을 맞춥니다.

type RunResult = { exitCode: number | null; timedOut: boolean };
type Executor = (task: string) => Promise<RunResult>;

const fakeExecutor: Executor = async (_task) => ({
  exitCode: 0,
  timedOut: false,
});

완료 기준: 실행기를 교체해도 조율 함수의 분기 구조가 제공자 이름에 의존하지 않게 만듭니다. 성공·오류·시간 초과 세 경우와, 중지 요청 뒤 늦게 도착한 결과를 어떻게 취급할지 테스트 또는 상태표로 제시합니다.

과제 5고급 · 1~2일

“완료 알림 뒤 연결이 끊겼다” 복구 시나리오 설계

상황은 이렇습니다. 실행 결과는 접수됐지만 파일 정리와 비용 정산이 남았고, 서버가 재시작했습니다. 여기에 브라우저는 완료 이벤트를 놓쳤습니다. 각 계층이 무엇을 근거로 복구해야 하는지 설계하세요.

완료 기준: 세 가지 장애 시점의 상태 전이표와 검증 계획을 만듭니다. run-completion.ts, recovery.ts, Rust의 state.rs, LiveUpdatesProvider.tsx에서 각 책임에 대응하는 코드를 하나씩 연결합니다.

9관련 기술 심화 학습 로드맵

주 5~7시간을 가정한 6주 과정

JavaScript 함수·배열·객체와 기본 HTML을 안다는 가정의 학습안입니다. 처음 보는 문법이 많으면 1~2주차를 늘려도 좋습니다. 매주 하나의 동작을 설명하는 작은 결과물을 남기면 큰 저장소를 이해한 범위가 눈에 보입니다.

주차배울 내용연결할 코드주간 결과물
1주차TypeScript 객체·유니언·제네릭, Zod 입력 검증packages/shared/src/validators/issue.ts정상·잘못된 checkout JSON 5개와 실패 이유
2주차HTTP, 미들웨어, 인증과 인가, 서비스 분리server/src/middleware/validate.ts·server/src/routes/issues.ts요청부터 서비스 호출까지의 흐름도
3주차관계형 모델, 인덱스, 조건부 UPDATE, 트랜잭션 격리packages/db/src/schema/issues.ts·서비스 checkout()두 연결의 업무 선점 경쟁 결과표
4주차React 상태, 서버 캐시, WebSocket, 재연결ui/src/context/LiveUpdatesProvider.tsx정상·중복·누락 이벤트의 캐시 변화표
5주차전략 패턴, 실행 상태 전이, 취소·재시도packages/adapter-utils/src/types.ts·server/src/services/heartbeat/가짜 실행기와 실패·중지 시나리오
6주차Rust의 Result·소유권 기초, 영속 저장, CI 검증 범위packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs·.github/workflows/pr-trusted.yml재시작 복구 설계와 검증 계획

앞의 3주는 데이터와 요청의 규칙을 이해하는 데 쓰고, 뒤의 3주는 여러 실행과 화면이 그 규칙을 공유하는 방법을 살펴봅니다. Rust 문법에서 막히면 파일 저장의 순서부터 한국어로 적은 뒤 각 호출에 대응시켜 보세요.

9-1. 공식 문서와 코드를 왕복하는 방법

진도 확인
다음 질문에 파일을 가리키며 답할 수 있는가

“왜 같은 업무를 두 실행이 동시에 잡지 못하는가?”, “연결이 끊겼는데 왜 바로 실패 처리하지 않는가?”, “완료했는데 왜 정산·정리가 남을 수 있는가?”, “화면은 놓친 이벤트를 어떻게 복구하는가?” 파일과 조건문을 근거로 설명할 수 있다면 문법을 넘어 설계를 읽기 시작한 것입니다.

10핵심 키워드 사전

이름을 외우기보다 이 저장소의 역할과 연결한다
용어쉬운 설명
에이전트목표를 받고 모델과 도구로 작업하는 실행 주체. 여기서는 담당 업무·설정·상태를 가진 관리 대상입니다.
오케스트레이션여러 작업의 시작 조건·순서·협업·종료를 조율하는 것. 호출을 여러 번 하는 것보다 넓은 개념입니다.
제어 평면누가 어디에서 무엇을 실행할지 정하고 상태와 권한을 관리하는 부분입니다.
하네스 (harness)모델에게 지침·도구·작업 환경을 제공하며 실행을 이어 가는 틀입니다.
어댑터서로 다른 제공자의 실행 방식과 결과를 공통 계약에 맞춰 연결하는 부품입니다.
Runner실제 실행 환경에서 프로세스와 세션·통신 등을 다루는 실행기입니다.
heartbeat이 저장소에서는 에이전트를 깨워 작업을 수행하게 하는 실행 흐름에 쓰는 이름입니다. 단순 생존 확인 신호만을 뜻하지 않습니다.
깨우기 (wakeup)배정·후속 메시지·예약 등의 계기로 실행을 요청하는 것. 요청 접수가 곧 즉시 실행 허가는 아닙니다.
issue / run업무 / 그 업무를 하려는 실행 한 번. 같은 업무에 여러 실행 기록이 연결될 수 있습니다.
checkout이 문맥에서는 업무 소유권을 확보하는 동작입니다. Git 브랜치를 바꾸는 명령과 구분합니다.
원자적 갱신하나의 변경을 중간 상태 없이 적용하는 것. 상태 조건을 UPDATE에 넣어 경쟁을 제어할 수 있습니다.
멱등성같은 요청이 다시 와도 불필요한 추가 효과가 생기지 않는 성질입니다.
인가신원을 확인한 뒤, 그 주체가 이 회사·업무에서 이 행동을 해도 되는지 판단하는 것입니다.
회사 범위데이터와 권한을 특정 회사에 묶는 경계. 다른 회사의 ID를 넣어도 접근 검사를 통과해서는 안 됩니다.
임대 (lease)일정 조건이나 기간 동안 리소스를 사용할 권리. 소유자가 사라질 때의 회수·인계 규칙도 필요합니다.
상태 머신가능한 상태와 상태 사이의 이동 조건을 명시한 모델입니다.
영속성프로세스가 끝나도 필요한 정보가 남는 성질입니다. 메모리의 Map만으로는 얻을 수 없습니다.
재생 (replay)저장된 명령·이벤트를 다시 읽어 상태를 따라잡는 방식. UI 알림과 Runner 경로의 보장이 다릅니다.
캐시 무효화저장해 둔 데이터가 오래됐을 수 있다고 표시하는 것. 활성 쿼리의 재조회와 이어질 수 있습니다.
예산 예약실제 비용 확정 전, 진행할 실행을 위해 가용 예산 일부를 잡아 두는 개념입니다.
정산보고된 사용량·가격·중복 여부를 다뤄 비용 기록에 반영하는 과정입니다.
SHA-256파일 내용에서 계산하는 해시값. 받은 내용이 예상과 같은지 비교하며, 접근 권한을 대신 판정하지는 않습니다.
MCPAI 클라이언트와 도구·자원을 제공하는 서버를 연결하는 표준 프로토콜입니다.
스키마 / 마이그레이션데이터 구조의 정의 / 그 구조를 버전별로 바꾸는 절차입니다.
잠금 파일의존성 해석 결과를 기록한 파일. 선언한 버전 범위와 구분해서 읽어야 합니다.
모의 구현 (mock)외부 서비스를 실제 호출하지 않고 정해진 응답·실패를 만들어 검증하는 대체물입니다.
CI변경이 들어올 때 정책·타입·테스트·빌드 같은 검사를 자동으로 수행하는 체계입니다.

한 단어가 서로 다른 계층에서 다른 뜻으로 쓰일 수 있습니다. 예를 들어 업무의 done, 실행의 succeeded, 파일 정리의 완료는 같은 상태가 아닙니다. 로그를 읽을 때는 “무엇의 상태인가”를 먼저 확인하세요.

11참고 링크

설명한 구현은 커밋 412914c에 고정해서 읽는다

11-1. 공식 저장소·제품 문서

11-2. 본문과 함께 열어 볼 핵심 소스

11-3. 관련 기술 공식 자료

현재 상태 (2026-10-23 확인)

항목기록
저장소paperclipai/paperclip
선정 기준GitHub Trending Monthly #1 · 2026-10-23 분석
스타 / 포크106,337 / 17,680 · 2026-10-23 GitHub 기준
생성일2026-03-02
주 언어 / 라이선스TypeScript / MIT · 루트 LICENSE에서도 확인
최신 릴리스v2026.1012.0 · 2026-10-12
최근 push2026-10-23 · GitHub 기준
분석 기준 최근 커밋412914c · 2026-10-22 23:38:09 -07:00 · 실행 호스트에서 native 플러그인 업무 출력 파일 읽기 (#17285)
커밋의 한국 시각2026-10-23 15:38:09 +09:00 · 날짜 차이는 시간대에 따른 것
내부 패키지 버전서버·UI·DB 등 0.3.1 · 날짜형 릴리스 태그와 별도 값
코드 규모TS·TSX 2,607,502줄 / Rust 80,775줄. 집계 대상 11개 확장자 전체 2,822,544줄·9,395파일
집계 범위테스트·스토리·생성 코드·빈 줄·주석 포함. 의존성·빌드·캐시 디렉토리와 Markdown 등 문서 형식·JSON·잠금 파일 제외
쓰기 전에 알아둘 한계
제품의 목표·구현 사실·환경별 보장을 구분한다

README의 “회사를 운영한다”는 표현은 제품의 지향점입니다. 실제 업무 성과·완전 자율성·수익을 보장하지 않습니다. 트렌딩 순위와 스타 수 역시 성능·보안·사용자 수의 측정값이 아닙니다. 주목받는 이유에 관한 해석과 구현에서 확인되는 조건을 구분해서 읽어야 합니다.

설명한 경로는 커밋 412914c 기준이며, 더 최신 코드와 릴리스된 패키지는 다를 수 있습니다. 단일 커밋의 파일만으로 전체 커밋 수·기여자 비중·과거 구현의 변화까지 판단할 수 없습니다. 줄 수에는 생성 파일과 테스트가 포함되므로 유지보수 규모의 대략적인 지표로만 활용하세요.

공식 문서의 지원 범위와 실제 호스트에서의 실행 가능성도 별도로 확인해야 합니다. 본문의 CPU·RAM·디스크 권장은 추정이며 처리량·비용 상한의 실측 보증이 아닙니다. 회사 범위 검사와 복구 코드가 있다는 사실만으로 모든 배포의 격리·장애 복구가 자동으로 검증되는 것은 아닙니다.