에이전트 앱
8c65bc7 기준입니다.rakazo는 사용자가 만든 봇에게 모델·도구·기억·실행 환경을 붙여 주는 자체 호스팅 AI 에이전트 제품입니다. 모델이 다음 행동을 제안하면, 서버가 그 행동의 실행과 기록, 대기와 재개를 관리합니다. 이 역할 분담이 전체 코드를 읽는 출발점입니다.
README는 이를 지속적으로 함께 일하는 AI 동료라고 소개합니다. 실제 데이터 모델도 그 설명과 연결됩니다. packages/db/prisma/schema.prisma에는 봇을 나타내는 Bot, 대화 단위인 Thread, 실행 한 건인 Run, 예약 작업인 Routine, 실행 컴퓨터인 Computer가 각각 있습니다. 한 봇의 존재와 한 번의 실행을 서로 다른 수명으로 다룹니다.
모델은 생각하는 직원, 샌드박스는 직원이 쓰는 책상과 컴퓨터입니다. API는 요청을 받는 접수 창구, 워커는 업무를 진행하는 담당자, PostgreSQL은 업무 대장에 해당합니다.
접수 창구를 닫아도 대장에 남은 일은 사라지지 않아야 합니다. 담당자가 바뀌어도 누가 어떤 일을 맡았는지 알아야 합니다. rakazo의 흥미로운 부분은 바로 이 운영 규칙입니다.
예를 들어 외부 모델 API와 로컬 Docker 컴퓨터를 조합할 수도 있고, 로컬 모델 서버와 원격 컴퓨터를 조합할 수도 있습니다. packages/adapters/src/pi-local-provider.ts에는 로컬 모델 연결이, packages/adapters/src/sandbox-factory.ts에는 컴퓨터 제공자를 선택하는 코드가 따로 있습니다. 조합 가능성은 인터페이스가 만들며, 실제로 쓸 수 있는 도구는 각 제공자의 기능에 따라 달라집니다.
TypeScript 타입을 API 경계까지 연결하는 방법, 오래 걸리는 일을 웹 요청에서 분리하는 방법, 재시도에도 중복을 줄이는 방법, 실시간 화면을 저장된 기록과 맞추는 방법을 배웁니다. 모델 학습이나 추론 엔진 구현이 중심인 저장소는 아닙니다.
2026-10-23 GitHub Trending TypeScript Weekly에서 8위로 선정된 프로젝트입니다. 스타 5,309개와 포크 946개는 관심의 크기를 보여 주지만, 그 수치만으로 유입 경로나 인기의 원인을 증명할 수는 없습니다. 여기서는 관심을 끌 만한 특징을 소스에 근거해 해석합니다.
README의 “원하는 모델과 샌드박스를 고른다”는 설명은 AgentRuntime과 SandboxProvider라는 별도 인터페이스로 구체화됩니다. 둘 다 packages/adapter-kit/src/interfaces.ts에 있습니다. 모델 SDK가 바뀔 때 화면과 데이터베이스 코드를 함께 갈아엎지 않도록 경계를 둔 점이 학습 가치입니다.
대화 이력, 기억 문서, 예약 실행, 다른 봇에 대한 위임은 서로 다른 데이터와 도구로 표현됩니다. 특히 Run에는 상태·임대 만료 시각·체크포인트가 있고, Routine은 다음 실행을 관리합니다. “계속 일한다”는 제품 문구를 구현하려면 서버 재시작과 사용자 부재까지 다뤄야 한다는 사실을 보여 줍니다.
README와 AGENTS.md는 웹·데스크톱·모바일을 하나의 제품으로 설명합니다. Electron은 웹 UI를 호스팅하고, Expo는 네이티브 클라이언트를 제공합니다. 공유 계약과 서버 중심의 실행 관리는 기기마다 서로 다른 결과가 생기는 것을 줄이는 방향입니다. 화면의 세부 기능과 언어 지원까지 모두 같다는 뜻은 아닙니다.
| 비교 관점 | 일반적인 접근 | rakazo에서 확인할 장점과 부담 |
|---|---|---|
| 관리형 AI 비서 | 서비스가 정한 모델·실행 환경·운영 정책을 이용합니다. | 모델과 컴퓨터를 분리해 고를 수 있습니다. 대신 계정 연결, 업데이트, 백업을 직접 관리할 범위가 넓어집니다. |
| 터미널 중심 도구 | 명령줄과 코드 작업이 주된 접점인 형태입니다. | 웹·모바일·데스크톱에서 대화와 컴퓨터 작업을 함께 다룹니다. 그만큼 UI·인증·네트워크 경계가 많습니다. |
| 직접 만든 대화 데모 | 요청 한 번에서 모델 응답을 받아 반환하는 구현입니다. | 저장된 실행, 작업 큐, 재개, 이벤트 복구를 살필 수 있습니다. 입문자가 전체를 한 번에 읽기에는 규모가 큽니다. |
| 자체 호스팅 앱 | 데이터와 배포를 직접 운영하는 형태입니다. | 게시 이미지와 Compose 구성이 있습니다. 외부 모델·앱 연결을 쓰면 해당 데이터 전송과 비용은 별도로 생깁니다. |
이 표는 접근 방식의 차이를 설명한 것입니다. README는 Grok Bot, Meta Muse, OpenAI Dots, Instinct, OpenClaw, Hermes Agent의 대안이라고 소개하지만, 이 글은 각 제품의 최신 기능·가격·성능을 실측한 비교가 아닙니다. 특정 경쟁 제품보다 빠르거나 저렴하다고 단정하지 않습니다.
이 저장소의 학습 가치는 모델 호출을 실제로 운영할 수 있는 업무 시스템으로 확장하는 과정을 볼 수 있다는 데 있습니다. 어댑터 하나를 교체하는 문제와, 재시도 중 외부 행동을 중복 실행하지 않는 문제를 함께 배울 수 있습니다.
README는 베타 제품이라고 명시합니다. 서버를 직접 실행해도 외부 모델, 음성, 앱 연결을 선택하면 네트워크와 서비스 자격 증명이 필요합니다. 로컬 모델 경로는 있지만 추론 서버와 모델 파일을 앱이 함께 제공하는 구조는 아닙니다.
이 저장소는 pnpm-workspace.yaml로 연결한 모노레포입니다. apps/*, packages/*, 샌드박스 supervisor, updater를 묶으며 해당 범위의 package.json은 20개입니다. 아래 버전은 커밋 8c65bc7의 각 의존성 선언값입니다. 범위 없는 고정 버전이 많지만 모든 패키지가 같은 도구 버전을 쓰지는 않습니다.
workspace:*는 공개 레지스트리에서 받는 대신 이 저장소 안의 패키지를 연결하라는 pnpm 표기입니다. 코드의 위치와 배포 프로세스의 수는 서로 다릅니다.| 기술·버전 | 실제 역할 | 선언 파일 |
|---|---|---|
| Hono 4.13.13 | HTTP 앱, 인증·헬스·화면 프록시 등 서버 라우트 | apps/api/package.json |
| oRPC 1.15.7 | 공유 계약과 서버 핸들러·클라이언트 호출 연결 | apps/api/package.json의 @orpc/server, packages/contracts/package.json의 @orpc/contract |
| Zod 4.6.7 | API 입력·출력과 작업 페이로드의 실행 시점 검증 | packages/contracts/package.json, packages/adapter-kit/package.json |
| Prisma 7.10.1 | 데이터 스키마, 클라이언트 생성, 마이그레이션 | packages/db/package.json의 CLI·클라이언트·@prisma/adapter-pg |
| pg 8.23.1 | PostgreSQL 연결 풀, 알림과 잠금 등 SQL 기능 사용 | packages/db/package.json, packages/adapters/package.json |
| Better Auth 1.7.11 | 서버 인증과 계정 기능, Expo·passkey 연동 | packages/auth/package.json |
| Graphile Worker 0.17.3 | PostgreSQL 기반 백그라운드 작업 처리 | packages/adapters/package.json |
| Pi 1.3.0 | 모델·도구 호출과 스트리밍을 런타임 어댑터에서 연결 | packages/adapters/package.json의 @earendil-works/pi-agent-core, pi-ai, pi-telemetry |
apps/api/src/app.ts는 인증·저장소·런타임·커넥터 등을 조립하고, apps/worker/src/index.ts는 실행기와 작업 호스트를 조립합니다. HTTP 서버인 Hono가 모델을 직접 구현하는 것도, Graphile Worker가 봇의 상태 전체를 정의하는 것도 아닙니다. 각각의 역할 사이에 저장된 도메인 상태가 있습니다.
| 영역 | 의존성 선언 | 읽을 때의 초점 |
|---|---|---|
| 웹 | React·React DOM 19.2.3, React Router 7.18.4, Vite 8.3.4, Tailwind CSS 4.3.3 | apps/web/package.json. UI 상태와 RPC 구독이 만나는 지점을 찾습니다. |
| 웹 다국어 | Lingui 6.6.0 | apps/web/package.json. 문구를 코드와 번역 카탈로그로 나누는 방식을 봅니다. |
| 데스크톱 | Electron 44.9.1, electron-builder 26.15.3, electron-updater 6.8.9 | apps/desktop/package.json. 웹 화면과 네이티브 셸·로컬 서버 설정의 경계를 봅니다. |
| 모바일 | Expo 57.0.31, React Native 0.86.3, Expo Router 57.0.29, Reanimated 4.5.1 | apps/mobile/package.json. 공유 계약을 쓰되 내비게이션·권한·저장은 플랫폼별로 처리합니다. |
| 공개 웹사이트 | Astro 7.4.1, @astrojs/react 6.0.6 | apps/www/package.json. 제품의 대화 앱과 소개 사이트는 별도 앱입니다. |
루트 package.json의 override는 React와 React DOM을 19.2.3으로 맞춥니다. 반면 TypeScript는 루트·대부분 서버 패키지에서 7.0.2, 모바일·Astro 사이트에서 6.0.3입니다. README의 “TypeScript, React 19”라는 요약만으로는 보이지 않는 차이입니다.
| 연결 분야 | 선언된 SDK 예시 | 구분할 점 |
|---|---|---|
| 원격 컴퓨터 | @e2b/desktop 2.4.0, @daytona/sdk 0.211.2, @boatdev/sdk 1.10.0 | 설치 의존성에 있다고 모든 제공자가 활성화되지는 않습니다. |
| 앱·도구 연결 | @composio/core 0.18.1, @modelcontextprotocol/sdk 1.30.0 | 관리형 카탈로그와 사용자가 등록하는 도구 소스를 구분합니다. |
| Docker 제어 | dockerode 5.0.1 | infra/sandboxes/supervisor/package.json에 있습니다. 일반 API와 Docker 제어 프로세스를 나눕니다. |
위 SDK 버전 중 supervisor를 제외한 선언 위치는 packages/adapters/package.json입니다. CreateOS·Railway·Pipedream 같은 기능도 있지만, 모든 기능이 같은 형태의 전용 SDK를 의존성으로 갖는다고 가정해서는 안 됩니다. 제공자별 파일과 생성 코드를 함께 읽어야 합니다.
engines.node는 ^22.22.2 || ^24.0.0 || >=26.0.0. Node 23·25는 이 범위에 없습니다.packageManager는 pnpm@9.15.0. pnpm-lock.yaml이 설치 해석을 고정합니다.2.11.9. turbo.json의 build는 ^build와 ^generate를 먼저 요구합니다.scripts/build-server.mjs를 앱의 build 스크립트가 호출하며 루트에는 esbuild 0.28.2가 선언됩니다.2.5.15, Vitest 4.1.12. 모바일은 Vitest 4.1.11을 별도로 선언합니다.1.64.1. packages/testkit에는 PostgreSQL용 Testcontainers 12.2.1이 있습니다.infra/compose/Dockerfile은 digest가 고정된 node:22-bookworm 기반입니다. Compose의 PostgreSQL 기본 이미지는 postgres:16입니다..github/workflows/ci.yml에는 lint, 타입 검사, 빌드, 단위 테스트, PostgreSQL 통합 여정, 웹 E2E가 나뉘어 있습니다. 데스크톱 화면 검증은 Linux 가상 디스플레이에서 실행하도록 구성됩니다. 이는 정의된 검증 경로이며 특정 커밋의 모든 실행이 통과했다는 결과 보고는 아닙니다.
타입 검사는 설계 도면의 규격 검사, 단위 테스트는 부품 검사, 통합 테스트는 배관 연결 검사, E2E는 사람이 수도꼭지를 틀어 보는 검사입니다. 서로 다른 결함을 찾기 때문에 이름이 여러 개 붙어 있습니다.
아래는 기본적인 Docker·PostgreSQL 배포를 중심으로 그린 구조도입니다. 화살표는 주요 의존·데이터 흐름입니다. 선택 기능인 음성·결제·외부 메모리·메신저와 초기화·업데이트 서비스는 생략했습니다. API와 워커가 여러 어댑터를 공유하므로 모든 모델·도구 호출이 오직 한 프로세스에서만 발생한다고 해석하면 안 됩니다.
packages/adapter-kit/src/interfaces.ts는 외부 시스템을 호출하는 통로를 인터페이스로 정의합니다. AgentRuntime, SandboxProvider, MemoryStore, JobPublisher가 대표적입니다. 실제 구현을 바깥에서 골라 전달하므로 실행 로직은 “어느 회사 SDK인가”보다 “어떤 일을 요청할 수 있는가”에 의존합니다.
// packages/adapter-kit/src/interfaces.ts에서 발췌
export interface AgentRuntime {
describe(): AdapterDescriptor<AgentRuntimeCapabilities>;
run(
request: AgentRunRequest,
context?: Partial<AdapterContext>,
): AsyncIterable<AgentRuntimeEvent>;
abort(runId: string): Promise<void>;
}
여기서 AsyncIterable은 결과를 한 번에 돌려주는 대신, 시간이 지나며 여러 이벤트를 내보낸다는 계약입니다. 호출자는 for await...of로 텍스트·도구 요청·완료 같은 사건을 차례로 처리할 수 있습니다. 그 계약에 Pi 기반 구현과 테스트용 구현이 모두 들어맞습니다.
apps/worker/src/index.ts의 main()은 AGENT_RUNTIME === "scripted"이면 ScriptedAgentRuntime, 아니면 PiAgentRuntime을 만듭니다. 이어 createRunExecutor({ runtime, sandbox, memory, jobs, ... })로 필요한 구현을 전달합니다. 이런 조립 지점을 구성 루트라고 부릅니다.
// packages/adapters/src/sandbox-factory.ts에서 발췌
case "docker":
return new DockerSandboxProvider(
opts.supervisorUrl ?? "http://127.0.0.1:7091",
opts.supervisorToken,
);
case "fake":
return new FakeSandboxProvider();
createSandboxProvider()는 다른 분기에서 E2B·Daytona·CreateOS·Boat·Railway 등을 선택합니다. 환경변수 해석은 packages/adapters/src/sandbox-provider-env.ts의 resolveSandboxProvider()가 담당합니다. 원격 제공자를 지정했어도 필수 키가 없으면 none으로 내려가는 경로가 있습니다. “앱이 켜졌다”와 “컴퓨터 작업이 가능하다”는 다른 상태입니다.
apps/api/src/router.ts의 threads.send는 모델 설정과 대화 대상을 확인한 뒤 apps/api/src/thread-target.ts의 sendThreadMessage()를 호출합니다. 이 함수에는 clientNonce로 기존 전송을 재확인하는 경로와 데이터베이스 트랜잭션이 있습니다. 새 요청과 이미 접수된 요청을 구분하는 장치입니다.
실행을 깨우는 enqueueRunsNeedingContinue()는 같은 파일에 있습니다. 여기에는 큐 등록이 실패해도 이미 저장된 queued 실행은 남으며 reconciler가 복구한다는 주석과 오류 처리 코드가 있습니다. 메시지 수신과 모델 실행을 하나의 긴 HTTP 요청으로 묶지 않습니다.
// packages/adapter-kit/src/background-jobs.ts에서 발췌
export function runContinueJob(runId: string): BackgroundJob {
return {
name: "run.continue",
payload: { runId },
replaceKey: runJobKey(runId),
};
}
작업 페이로드에는 전체 대화나 키를 복사하는 대신 식별자인 runId를 넣습니다. packages/adapters/src/wakeup.ts의 GraphileJobPublisher.enqueue()는 이를 utils.addJob()으로 등록하며 replaceKey를 Graphile의 jobKey로 전달합니다. 중복 키 처리에서는 남은 시도 횟수와 오래된 잠금도 고려합니다.
packages/adapters/src/job-reconciler.ts의 createJobReconciler()는 기본 30_000ms 간격으로 저장 상태를 점검합니다. createPostgresReconciliationLeadership()는 PostgreSQL advisory lock으로 조정 담당자를 정합니다. 워커가 여럿일 때 모두가 같은 복구 점검을 주도하지 않도록 하는 구성입니다.
업무 대장에 “처리 대기”라고 적은 뒤 담당자에게 알림을 보냈는데 알림만 실패했다고 합시다. 주기적으로 대장을 훑는 관리자가 있으면 그 일을 다시 전달할 수 있습니다. 큐를 통한 즉시 전달과 저장 상태를 기준으로 한 복구 점검을 함께 두는 이유입니다.
이것을 “모든 외부 행동이 정확히 한 번 실행된다”는 보장으로 확대하면 안 됩니다. 큐 중복 제어, 실행 임대, clientNonce, 외부 효과 기록은 서로 다른 경계의 중복을 다룹니다. 원격 API에서 행동은 성공했지만 응답만 사라진 경우에는 해당 제공자의 멱등성 지원까지 고려해야 합니다.
packages/core/src/run-state.ts에는 허용된 상태 전이 표와 canTransition(), assertTransition(), nextFence()가 있습니다. 버튼마다 상태 문자열을 임의로 바꾸는 대신 공통 규칙을 만듭니다. 아래는 주요 경로를 추린 그림이며 모든 취소·복구 경로를 나타낸 것은 아닙니다.
packages/adapters/src/executor.ts의 createRunExecutor()가 반환하는 continueRun(runId, workerId)는 조건부 updateMany()로 실행을 선점합니다. 상태와 기존 선점 조건이 맞는 행 하나를 바꿔야 다음 단계로 갑니다. 확인한 뒤 수정하는 두 동작 사이에서 다른 워커가 끼어드는 경쟁을 줄이는 방식입니다.
// continueRun()의 선점 데이터와 결과 검사 부분 발췌
data: {
status: "leased",
leaseOwner: workerId,
leaseFence: fence,
leaseExpiresAt: new Date(Date.now() + 5 * 60_000),
error: null,
checkpoint: null,
},
});
if (leased.count !== 1) return;
leaseFence는 nextFence(run.leaseFence)로 증가시킵니다. 이어 running으로 바꾸는 쓰기에도 leaseOwner와 leaseFence를 조건으로 넣습니다. 이전 담당자가 늦게 돌아와 최신 담당자의 상태를 덮어쓰는 문제를 막는 데 쓰는 값입니다. 예시의 5분은 최초 임대 만료 설정이며 모든 작업이 5분에 종료된다는 뜻은 아닙니다.
waiting_input은 활성 상태지만 새 작업의 진입을 막는 목록에서는 제외됩니다. waiting_takeover는 그 목록에 남습니다. QUEUE_HOLDING_RUN_STATUSES와 해당 테스트를 나란히 읽으면 “사용자에게 질문 중”과 “컴퓨터를 사용자에게 넘긴 상태”가 왜 다르게 취급되는지 추론할 수 있습니다.
packages/db/prisma/schema.prisma의 Event에는 대화별 순서인 seq가 있고 @@unique([threadId, seq])가 선언됩니다. packages/db/src/events.ts의 followThreadEvents()는 마지막으로 읽은 순번 뒤의 이벤트를 데이터베이스에서 읽어 내보냅니다.
// followThreadEvents()의 읽기 루프에서 발췌
const events = await eventsAfter(prisma, threadId, seq, EVENT_BATCH_SIZE);
batchSize = events.length;
for (const event of events) {
seq = event.seq;
yield mapProductEvent(event);
}
EVENT_BATCH_SIZE는 200입니다. packages/adapters/src/realtime.ts의 PostgresRealtimeFanout은 LISTEN과 pg_notify로 새 기록이 있음을 알립니다. 알림이 전체 기록을 대신하는 구조는 아닙니다. 구독자는 신호를 받으면 저장된 이벤트를 읽고, 알림이 없어도 주기적으로 뒤처진 기록을 확인합니다.
그 확인 간격은 realtime 사용 시 기본 30_000ms, 알림 없이 폴링만 할 때 400ms입니다. RPC 구독은 apps/api/src/router.ts의 threads.subscribe에서 연결되며 20_000ms 하트비트와 세션 재확인 경로도 있습니다. 세션 재확인 기준은 10_000ms이지만 실제 확인은 이벤트 전달 또는 하트비트 시점에 일어납니다.
실시간 알림은 빨리 알려 주는 신호이고, 연결이 끊겼을 때 복구할 근거는 저장된 이벤트입니다. apps/web/src/pages/Shell.tsx의 rpc.threads.subscribe({ ..., cursor }, { signal })가 이 서버 계약을 소비하는 예입니다.
이벤트 기록을 쓴다고 제품 전체가 순수한 이벤트 소싱 구조인 것은 아닙니다. Message, Run, Bot 같은 현재 상태 테이블도 함께 갱신합니다. 저장된 이벤트의 재전달과 현재 상태 저장을 조합한 구조로 이해하는 편이 정확합니다.
클라이언트가 전달한 대화 ID만으로 모든 데이터를 읽지 않습니다. API의 대상 해석·멤버십 검사와 함께 packages/db/src/row-security.ts가 runAsTenant()로 공간·사용자 문맥을 전달합니다. runAsSystem(reason, fn)은 여러 공간을 다뤄야 하는 명시적 우회 경로입니다. 실제 보호는 전용 앱 DB 계정과 적용된 정책이 함께 있어야 성립합니다.
도구 승인 규칙은 packages/core/src/action-approval.ts에서 읽을 수 있습니다. connectorToolRequiresApproval()는 선언된 쓰기 여부와 도구 이름의 변경 동작을 검사합니다. 다만 shell·write_file 등이 기본 승인 면제 목록에 있으므로 “모든 파일 쓰기와 명령이 매번 승인된다”고 설명하면 틀립니다. 승인 정책과 컴퓨터 격리·호스트 권한은 함께 검토해야 합니다.
| 알고 싶은 것 | 시작 파일 | 다음에 이어 읽을 곳 |
|---|---|---|
| 메시지를 보내면? | apps/api/src/router.ts의 threads.send | apps/api/src/thread-target.ts → packages/db/src/events.ts → packages/adapters/src/executor.ts |
| 제공자를 바꾸면? | packages/adapter-kit/src/interfaces.ts | packages/adapters/src/sandbox-factory.ts → 제공자별 구현 → apps/worker/src/index.ts |
| 화면이 갱신되면? | packages/contracts/src/events.ts | packages/db/src/events.ts → apps/api/src/router.ts → apps/web/src/pages/Shell.tsx |
폴더 분리가 항상 파일 크기까지 작게 만든다는 뜻은 아닙니다. apps/api/src/router.ts와 packages/adapters/src/executor.ts는 많은 기능을 담고 있습니다. 처음부터 파일 전체를 읽기보다 함수 이름을 검색하고 호출자·반환 타입·저장하는 필드를 따라가면 부담을 줄일 수 있습니다.
Git 추적 파일은 2,795개입니다. 확장자로 분류한 TypeScript는 .ts 442,549줄, .tsx 103,563줄로 합계 546,112줄입니다. 이 합계 중 파일명에 .test.가 있거나 경로에 /e2e/가 있는 파일은 291,901줄입니다. 나머지 254,211줄에도 테스트 보조 코드 등이 포함될 수 있습니다.
이 수치는 빈 줄·주석·선언 파일까지 포함한 물리적 줄 수입니다. 구현 규모나 품질·테스트 커버리지의 측정값이 아닙니다. lockfile, JSON, 문서, 이미지 등을 더한 전체 저장소 텍스트 줄 수와도 다릅니다. 언어별 나머지 집계는 마지막 상태 표에 정리했습니다.
구현 파일 옆의 테스트를 함께 읽으세요. packages/core/src/run-state.test.ts는 허용·금지 전이를, apps/api/src/thread-target.test.ts는 메시지 전달의 여러 경우를, packages/adapters/src/executor-effect-idempotency.test.ts는 외부 효과 중복 문제를 다룹니다. 테스트 이름을 요구사항의 목차처럼 쓰면 좋습니다.
TypeScript 타입은 편집기와 컴파일러가 사용하는 약속입니다. 네트워크로 들어온 JSON이 그 약속을 지키는지는 별도로 확인해야 합니다. packages/contracts/src/rpc.ts는 oRPC와 Zod로 API 계약을 표현하고, packages/adapter-kit/src/background-jobs.ts의 parseBackgroundJob()는 작업 이름에 맞는 페이로드를 검증합니다.
// packages/adapter-kit/src/background-jobs.ts의 스키마 일부
"run.continue": z.object({ runId: z.string().min(1) }),
"routine.wakeup": z.object({
routineId: z.string().min(1),
scheduledFor: z.string().datetime({ offset: true }),
}),
실습 아이디어: { runId: "" }, { runId: 42 }, { runId: "run-demo" }를 종이에 써 두고 어느 단계에서 거절되는지 예상해 보세요. 타입을 강제로 단언하는 as와 parse()가 왜 같은 검증이 아닌지 설명할 수 있으면 좋습니다.
kind: "bot"인지 kind: "group"인지에 따라 사용할 필드를 달리 정의하는 타입입니다. apps/api/src/thread-target.ts의 ThreadTarget이 예입니다. 분기를 확인하면 TypeScript가 해당 경우의 필드만 안전하게 사용하도록 좁혀 줍니다.Promise는 나중에 값 하나를 받는 표현이고, AsyncIterable은 시간이 흐르는 동안 값을 여러 번 받는 표현입니다. AgentRuntime.run()과 followThreadEvents()가 후자입니다. 취소에는 AbortSignal이 전달되며 구독을 닫을 때 정리할 작업도 생깁니다.
// 개념 실습용 독립 예제: 저장소 코드의 복사본이 아닙니다.
async function* studyEvents() {
yield { type: "text", text: "기록을 읽는 중" };
yield { type: "text", text: "결과를 정리하는 중" };
yield { type: "done", text: "완료" };
}
for await (const event of studyEvents()) {
console.log(event.type, event.text);
}
실습 아이디어: 두 번째 이벤트 뒤에서 읽기를 멈추면 생산자 쪽 자원을 어떻게 정리해야 할지 생각해 보세요. followThreadEvents()의 try/finally와 unsubscribe()를 찾아 위 작은 예제에 필요한 정리 지점을 표시합니다.
packages/db/prisma/schema.prisma의 Message는 [threadId, clientNonce]를 유일하게 유지합니다. Run은 [spaceId, clientNonce] 유일 제약과 [status, leaseExpiresAt] 인덱스를 가집니다. 앞의 제약은 중복 전송 구분에, 뒤의 인덱스는 실행 상태·만료 조건을 조회하는 경로에 대응합니다.
실습 아이디어: “같은 메시지 버튼을 두 번 눌렀다”와 “워커가 작업을 두 번 받았다”를 구분해 보세요. 전자는 전송 식별자, 후자는 실행 선점과 외부 효과 중복 방지가 중요합니다. 유일 제약 하나로 모든 중복 문제가 해결된다고 가정하지 않는 것이 학습 목표입니다.
packages/memory/src/index.ts의 MarkdownMemoryStore는 기억을 memory_documents와 memory_revisions 테이블에 저장합니다. 이름에 Markdown이 있어도 기본 저장 위치가 개별 Markdown 파일이라는 뜻은 아닙니다. exportMarkdown()·importMarkdown()은 휴대 가능한 파일 형태의 경계입니다.
commit()은 현재 내용 갱신과 revision 추가를 Serializable 트랜잭션으로 묶고, expectedRevision이 주어졌으면 현재 번호를 비교합니다. withTransactionRetry()는 충돌한 트랜잭션을 다시 시도하는 경로입니다. 사용자가 오래된 버전을 기반으로 쓴 요청을 조용히 덮어쓰지 않도록 하는 장치를 배울 수 있습니다.
// MarkdownMemoryStore.commit()에서 발췌
if (request.expectedRevision !== undefined) {
const live = existing?.revision ?? 0;
if (live !== request.expectedRevision) {
throw new Error(MEMORY_REVISION_CONFLICT_ERROR);
}
}
기본 search()는 문서와 경로를 소문자로 바꾼 뒤 includes(q)로 찾고 점수는 1을 반환합니다. 벡터 임베딩을 계산하는 구현이 아닙니다. packages/adapter-kit/src/interfaces.ts의 SemanticMemoryProvider는 선택적인 의미 기억 기능을 별도 계약으로 정의합니다.
실습 아이디어: “자동차”가 있는 기억에서 “승용차”를 검색하는 경우를 예상해 보세요. 문자열 포함 검사만으로는 동의어를 자동으로 알아내지 못합니다. 이후 단어 검색·형태소 분석·임베딩 검색을 붙일 때 각 방식이 추가하는 비용과 이점을 비교하면 좋습니다.
apps/web/src/lib/rpc.ts는 RPCLink와 createORPCClient()로 클라이언트를 만듭니다. apps/web/src/pages/Shell.tsx는 그 클라이언트로 대화 이벤트를 구독합니다. 타입을 공유해도 화면이 열릴 때의 구독, 대상을 바꿀 때의 취소, 네트워크가 끊겼을 때의 복원은 별도 로직이 필요합니다.
packages/ui-tokens/src/index.ts는 공유 디자인 토큰의 출발점이고, 웹 컴포넌트는 packages/ui-web에 있습니다. 모바일에는 apps/mobile/app의 경로와 apps/mobile/lib의 기기별 처리가 따로 있습니다. 공유할 도메인 규칙과 플랫폼에 맡길 내비게이션을 구분하는 사례입니다.
실습 아이디어: “그룹 이름을 누르면 설정을 연다”는 동작에서 서버 계약을 바꿔야 하는 부분과 화면 이동만 바꾸면 되는 부분을 나눠 보세요. 커밋 8c65bc7의 제목도 모바일 그룹 채팅 제목에서 설정을 여는 변경을 가리킵니다. UI의 작은 변화가 시스템 전체의 변경을 요구하지 않게 만드는 경계를 찾아봅니다.
packages/adapters/src/scripted-runtime.ts의 ScriptedAgentRuntime은 실제 모델 대신 정해진 흐름을 내보냅니다. FakeSandboxProvider, 메모리 기반 작업 큐와 실시간 알림 구현도 있습니다. 이 조합으로 모델 호출 비용과 외부 장애를 줄인 채 상태 변화와 관찰 가능한 효과를 확인할 수 있습니다.
packages/adapters/src/sandbox-conformance.test.ts, packages/adapters/src/browser-conformance.test.ts, packages/adapters/src/secret-store-conformance.test.ts 같은 파일은 구현이 공통 계약을 따르는지 살피는 출발점입니다. 가짜가 통과했다고 모든 실제 제공자의 네트워크·권한·응답 특성까지 확인됐다고 볼 수는 없습니다.
새 기술을 하나씩 외우기보다, 경계 하나와 그 경계가 실패했을 때의 행동을 묶어서 공부하세요. 타입에는 잘못된 JSON을, 큐에는 중복 수신을, 스트림에는 재접속을, 메모리에는 동시 수정을 붙여 생각하면 코드가 요구사항으로 읽힙니다.
README에는 앱 전체를 위한 단일 최소 RAM·CPU 표가 없습니다. 코드 읽기, 외부 모델을 쓰는 서버, 로컬 컴퓨터 컨테이너, 로컬 모델 추론은 요구 자원이 다릅니다. 특히 모델 추론에 필요한 GPU 메모리를 웹 앱 자체의 요구사항과 섞으면 안 됩니다.
| 사용 범위 | 확인된 시스템 조건 | 자원 해석 |
|---|---|---|
| 소스 독해 | 텍스트 편집기, 검색 도구, 선택적으로 Git·Python 3 | Node·Docker·API 키 없이도 구조와 계약을 공부할 수 있습니다. |
| 게시 이미지 사용 | README 기준 Docker Engine, Compose 플러그인, curl, OpenSSL | Node를 호스트에 설치하지 않는 경로입니다. 이미지·볼륨을 둘 디스크가 필요합니다. |
| 소스 개발 | 지원되는 Node 버전, pnpm 9, Docker, PostgreSQL | 루트의 정확한 pnpm 선언은 9.15.0입니다. 빌드와 네이티브 의존성으로 이미지 실행보다 준비 단계가 늘어납니다. |
| Docker 컴퓨터 | Linux 컨테이너에서 화면·브라우저·명령 실행 | 컴퓨터 한 대의 기본 상한은 메모리 2GiB·CPU 2코어 분량·PID 제한 2,048개입니다. |
| 원격 컴퓨터 | 제공자 계정·자격 증명·네트워크 | 브라우저·화면 작업 자원은 원격으로 옮겨지지만 앱 서버·DB 자원과 서비스 비용은 남습니다. |
| 로컬 모델 | 별도로 실행하는 OpenAI 호환 추론 서버와 모델 | RAM·GPU 요구량은 모델·양자화·문맥 길이에 따라 다릅니다. 이 저장소만으로 고정 사양을 제시할 수 없습니다. |
infra/sandboxes/supervisor/src/computer-spec.ts의 computerResourceLimits()가 Docker의 HostConfig에 들어갈 값을 계산합니다. parseMemoryBytes()는 g를 1024 ** 3으로 해석하므로 기본 2g는 2GiB입니다.
// infra/sandboxes/supervisor/src/computer-spec.ts의 기본값
const DEFAULT_COMPUTER_MEMORY = "2g";
const DEFAULT_COMPUTER_CPUS = "2";
const DEFAULT_COMPUTER_PIDS_LIMIT = "2048";
// computerResourceLimits()의 반환 값
return {
Memory: memoryBytes,
MemorySwap: memoryBytes,
NanoCpus: nanoCpus,
PidsLimit: pidsLimit,
};
MemorySwap을 Memory와 같은 값으로 설정하는 점이 눈에 띕니다. 소스 주석은 swap까지 포함한 상한이 예상보다 커지지 않게 하려는 의도를 설명합니다. 이 값들은 예약된 사용량이나 측정된 평균 사용량이 아니라 제한값입니다. 여러 봇이 컴퓨터를 공유할 수도 있으므로 봇 수와 컴퓨터 수를 그대로 같게 두면 안 됩니다.
한 컴퓨터의 자원 상한을 앱 전체의 최소 사양으로 읽지 않는 것이 용량 계획의 첫 단계입니다. PostgreSQL, API, 워커, 호스트 운영체제, 빌드 작업이 쓸 여유를 따로 남겨야 합니다.
외부 모델을 사용하고 로컬 Docker 컴퓨터 한 대만 켜 보는 연습이라면 4코어·RAM 8GB 이상·여유 디스크 20GB 이상을 계획상의 출발점으로 잡을 수 있습니다. 이것은 공식 최소 사양이나 실측 보장이 아닌 교육용 추정입니다. 병렬 빌드·브라우저 탭·이미지 캐시가 늘면 RAM 16GB와 더 넓은 디스크가 유리할 수 있습니다.
별도의 GPU는 외부 모델 API를 호출하는 앱 서버의 필수 구성요소로 선언돼 있지 않습니다. packages/adapters/src/pi-local-provider.ts는 기본 로컬 주소를 http://127.0.0.1:11434/v1, 기본 문맥을 32_768, 최대 출력 토큰을 4_096으로 설정합니다. 이 값은 연결 설정의 기본값이며 해당 크기의 모델이 이미 설치됐거나 모든 모델이 이를 지원한다는 뜻은 아닙니다.
linux/amd64·linux/arm64를 다룹니다. 호스트 OS 지원과 이미지 아키텍처는 구분해서 봅니다.5173, API 이미지 노출 포트에는 3100, supervisor 기본 포트는 7091이 있습니다. 내부 제어 포트를 곧바로 인터넷에 공개하는 구성이 아닙니다.5433을 기본으로 내보냅니다.사무실 크기를 정할 때 직원 한 명의 책상 크기만 곱해서는 복도와 회의실이 빠집니다. 컴퓨터 컨테이너의 상한은 책상 크기이고, DB·서버·운영체제·백업 공간은 공용 시설에 해당합니다.
처음 두 과제는 앱을 설치하지 않고 할 수 있습니다. 구현을 실험할 때는 별도의 개인 학습용 코드나 브랜치를 쓰고, 저장소의 전체 실행 흐름을 한꺼번에 올리기보다 작은 경계부터 다루세요. 아래 명령은 독자가 준비된 환경에서 선택할 수 있는 예시입니다.
시작: apps/api/src/router.ts에서 threads.send를 찾고 sendThreadMessage(), enqueueRunsNeedingContinue(), runContinueJob(), continueRun() 순서로 이동합니다.
만들 것: 함수 이름 다섯 개와 저장되는 Message·Run, 큐에 들어가는 runId를 표시한 한 장의 그림.
완료 기준: 모델 API를 기다리는 부분과 요청을 접수하는 부분을 구분하고, 큐 등록이 실패해도 어느 기록이 남는지 설명합니다.
확장 질문: clientNonce가 없는 요청과 있는 요청에서 중복 전송을 구분하는 근거가 어떻게 달라질까요?
# 저장소 루트에서 실행할 수 있는 읽기 전용 검색 예시
rg -n 'sendThreadMessage|enqueueRunsNeedingContinue' apps/api/src
rg -n 'runContinueJob|continueRun' packages/adapter-kit/src packages/adapters/src
rg -n 'clientNonce|leaseFence' packages/db/prisma/schema.prisma
시작: packages/core/src/run-state.ts의 전이 표를 카드로 옮깁니다. 상태는 queued, leased, running, waiting_input, waiting_takeover, completed, failed, cancelled의 여덟 가지입니다.
만들 것: 가능한 화살표와 불가능한 화살표가 표시된 상태도, 그리고 “실행 중으로 보임”과 “사용자 대기”를 나눈 표.
완료 기준: waiting_takeover → running과 completed → queued가 왜 허용되지 않는지 전이 표로 답합니다. failed → queued는 허용된다는 예외도 표시합니다.
확장 질문: 상태가 활성이라고 해서 항상 CPU를 사용 중이거나 새 작업을 막아야 할까요?
이미 의존성이 준비된 환경에서는 pnpm exec vitest run packages/core/src/run-state.test.ts로 해당 규칙의 테스트를 확인할 수 있습니다. 이 테스트에는 fast-check로 여러 입력 조합을 생성해 종료 상태의 전이를 확인하는 사례도 있습니다.
시작: followThreadEvents()의 커서와 seq만 참고해 독립적인 배열 기반 예제를 만듭니다. 데이터베이스나 실제 모델은 필요하지 않습니다.
만들 것: 순번 1~10의 이벤트 목록과 readAfter(cursor) 함수. 처음에는 1~4를 읽고, 연결을 끊었다고 가정한 다음 커서 4에서 다시 읽습니다.
완료 기준: 재개한 결과가 정확히 5~10인지 검사합니다. 알림을 일부러 생략해도 다음 확인 시 기록을 읽는다는 시나리오를 추가합니다.
확장 질문: 이벤트를 화면에 반영하기 전에 커서를 저장했다가 앱이 꺼지면 무엇을 놓칠 수 있을까요?
// 개념 실습용 독립 예제: 입력과 기대 결과를 명시합니다.
const events = Array.from({ length: 10 }, (_, i) => ({ seq: i + 1 }));
const readAfter = (cursor: number) => events.filter((e) => e.seq > cursor);
const resumed = readAfter(4).map((e) => e.seq);
// 기대 결과: [5, 6, 7, 8, 9, 10]
시작: MarkdownMemoryStore.commit()의 expectedRevision과 memoryRevision.create()를 읽습니다. 작은 메모리 객체로 버전 비교만 먼저 모사합니다.
만들 것: 같은 revision 3을 읽은 A와 B가 각각 저장하는 시나리오. A가 성공해 4가 된 다음 B가 이전 번호 3으로 저장할 때 충돌을 반환하도록 합니다.
완료 기준: B의 요청이 A의 결과를 조용히 덮어쓰지 않습니다. 실패한 요청에서 revision 기록만 추가되는 일도 없도록 현재 내용과 이력의 원자성을 설명합니다.
확장 질문: 트랜잭션 재시도와 사용자 버전 충돌은 왜 같은 복구 방법을 적용하면 안 될까요?
시작: createJobReconciler(), continueRun(), nextFence()를 읽습니다. 실제 서비스 대신 가짜 큐·가짜 시계·메모리 저장소를 준비합니다.
만들 것: “Run 저장 후 enqueue 실패”, “선점 후 워커 종료”, “새 워커 선점 뒤 옛 워커의 완료 응답 도착”의 세 시나리오를 재현하는 테스트 설계.
완료 기준: 대기 실행이 다시 전달되고, 현재 세대 번호가 아닌 상태 쓰기는 거절됩니다. 재시도한 외부 행동의 중복 문제는 별도 항목으로 남깁니다.
확장 질문: 원격 서비스가 멱등성 키를 지원하지 않을 때 성공 여부가 불명확한 호출을 자동 재시도해도 될까요? packages/adapters/src/executor-effect-idempotency.test.ts와 효과 기록 코드를 이어 읽어 보세요.
실습의 완료 기준은 화면이 한 번 잘 뜨는 것이 아니라, 정해 둔 실패 상황에서도 설명한 규칙이 유지되는 것입니다. 실패를 일부러 만들 수 있어야 복구 코드가 왜 필요한지 이해할 수 있습니다.
각 과제마다 “입력 상황 → 관찰한 상태 → 기대 상태 → 관련 함수” 네 칸만 기록해 보세요. 모델의 문장이 그럴듯한지보다 저장된 상태·순번·버전·호출 횟수처럼 확인 가능한 결과를 먼저 다룹니다.
주당 4~6시간을 가정한 학습 제안입니다. JavaScript의 함수·객체·배열과 기본적인 async/await를 먼저 알아두면 좋습니다. 일정은 제품의 공식 교육 과정이 아니며, 낯선 개념이 많으면 한 주를 두 주로 늘려도 됩니다.
| 주차 | 주제와 읽을 코드 | 손으로 만들 결과 | 다음 단계 기준 |
|---|---|---|---|
| 1주차 | TypeScript 타입·유니온·Zod. packages/contracts, packages/adapter-kit/src/background-jobs.ts |
정상·오류 입력 예시 6개와 작은 작업 페이로드 스키마 | 정적 타입과 실행 시점 검증을 구분해 설명합니다. |
| 2주차 | HTTP·인증·RPC. apps/api/src/router.ts, apps/web/src/lib/rpc.ts |
메시지 전송의 입력·출력·권한 검사 지도 | 잘못된 ID와 권한 없는 ID가 어느 경계에서 걸리는지 찾습니다. |
| 3주차 | 관계형 모델·유일 제약·트랜잭션. packages/db/prisma/schema.prisma, packages/memory/src/index.ts |
Bot·Thread·Run 관계도와 기억 수정 충돌 예제 | 현재 값과 변경 이력을 함께 저장해야 하는 이유를 설명합니다. |
| 4주차 | 작업 큐·상태 머신·재조정. packages/adapters/src/wakeup.ts, packages/core/src/run-state.ts, packages/adapters/src/job-reconciler.ts |
큐 등록 실패와 임대 만료의 시간 순서 그림 | 재시도가 필요한 이유와 재시도가 위험한 경우를 구분합니다. |
| 5주차 | 비동기 이터레이터·커서·취소. packages/db/src/events.ts, packages/adapters/src/realtime.ts, apps/web/src/pages/Shell.tsx |
순번 4에서 끊겼다가 5부터 이어 읽는 작은 구독 예제 | 알림 손실과 저장된 기록 손실의 차이를 말할 수 있습니다. |
| 6주차 | 어댑터 계약·RLS·컨테이너·CI. packages/adapter-kit/src/interfaces.ts, packages/db/src/row-security.ts, infra/sandboxes/supervisor/src/computer-spec.ts |
가짜 제공자 계약 검사와 공간 간 접근 실패 시나리오 | 어떤 테스트가 외부 서비스를 필요로 하는지 분류합니다. |
packages/adapters/src/pi-runtime.ts의 maxToolCallsPerTurn()는 에이전트 운영을 더 공부할 좋은 출발점입니다. MAX_TOOL_CALLS_PER_TURN이 비어 있거나 0이면 기본적으로 무제한을 뜻합니다. 제한값이 존재하는지와 기본으로 제한되는지는 다른 질문입니다.
학습 범위를 넓힐 때마다 정상 동작 하나와 실패 시나리오 하나를 짝지으면 설계 판단이 쌓입니다. 예를 들어 “새 제공자 연결” 다음에는 “키가 없는 제공자”, “구독 성공” 다음에는 “인증이 취소된 열린 구독”을 놓고 읽습니다.
건물을 공부한다면 외벽의 재료만 외우는 것보다 문 하나가 잠기고 열리는 과정을 따라가는 편이 구조를 이해하기 쉽습니다. 이 로드맵도 메시지 한 건이라는 작은 문에서 시작해 전기·배관에 해당하는 저장·큐·권한으로 넓혀 갑니다.
| 용어 | 쉬운 설명 | 이 저장소의 관찰 지점 |
|---|---|---|
| 자체 호스팅 | 앱 서버와 데이터를 직접 선택한 컴퓨터·서버에서 운영하는 방식입니다. | infra/compose, docs/self-host.md |
| 모노레포 | 여러 앱과 공통 코드를 하나의 저장소에서 관리합니다. | pnpm-workspace.yaml |
| 계약 | 입력·출력·가능한 동작에 대해 양쪽이 지켜야 하는 약속입니다. | packages/contracts, packages/adapter-kit |
| 어댑터 | 공통 약속을 특정 서비스의 API·SDK에 맞게 번역하는 코드입니다. | packages/adapters |
| 의존성 주입 | 사용할 구현을 내부에서 고정하지 않고 밖에서 넘겨 줍니다. | createRunExecutor(deps) |
| 구성 루트 | 실제 구현과 설정을 골라 하나의 앱으로 연결하는 지점입니다. | apps/worker/src/index.ts |
| RPC | 다른 프로세스의 기능을 정해진 입력·출력으로 호출하는 방식입니다. | oRPC 계약과 클라이언트 |
| 스키마 검증 | 실제로 들어온 값이 정해진 형태와 조건을 만족하는지 검사합니다. | Zod와 parseBackgroundJob() |
| 런타임 | 이 글에서는 모델 실행·이벤트·취소를 연결하는 실행 계층을 뜻합니다. | AgentRuntime, PiAgentRuntime |
| 샌드박스 | 명령과 브라우저 행동을 수행할 별도 실행 환경입니다. 격리 강도는 구성에 달려 있습니다. | SandboxProvider |
| 작업 큐 | 나중에 처리할 일을 넣고 워커가 꺼내게 하는 통로입니다. | GraphileJobPublisher |
| 워커 | 웹 요청과 별도로 오래 걸리는 일을 처리하는 실행 프로세스입니다. | apps/worker |
| 상태 머신 | 가능한 상태와 상태 사이의 이동 규칙을 명시한 모델입니다. | packages/core/src/run-state.ts |
| 임대 | 일정 시간 동안 특정 담당자에게 실행 권리를 주는 방식입니다. | leaseOwner, leaseExpiresAt |
| fencing token | 새 담당자에게 더 큰 번호를 줘 오래된 담당자의 쓰기를 구분합니다. | leaseFence, nextFence() |
| 멱등성 | 같은 요청을 반복해도 의도한 효과가 중복해 늘어나지 않게 하는 성질입니다. | 전송 nonce·외부 효과 기록·제공자별 지원 |
| 재조정 | 저장된 상태를 점검해 실행 누락·오래된 잠금 같은 틈을 메우는 과정입니다. | createJobReconciler() |
| advisory lock | 애플리케이션이 용도를 정해 사용하는 PostgreSQL 잠금입니다. | 재조정 리더 선정 |
| 트랜잭션 | 여러 저장 변경을 전부 성공하거나 전부 취소되는 한 묶음으로 처리합니다. | 기억 문서와 revision 동시 저장 |
| 낙관적 충돌 검사 | 읽었던 버전이 아직 최신인지 저장 시점에 확인합니다. | expectedRevision |
| 커서 | 목록이나 이벤트를 어디까지 읽었는지 가리키는 표식입니다. | followThreadEvents()의 cursor |
| 하트비트 | 새 소식이 없어도 연결이 살아 있음을 알리는 주기적 신호입니다. | HEARTBEAT_MS |
| 비동기 이터레이터 | 시간을 두고 도착하는 여러 값을 차례대로 읽는 방식입니다. | AsyncIterable, for await...of |
| RLS | 누가 접근하는지에 따라 DB 행의 읽기·쓰기를 제한하는 정책입니다. | packages/db/src/row-security.ts, 전용 앱 DB 계정 |
| 의미 검색 | 글자가 같지 않아도 의미가 가까운 정보를 찾는 검색 방식입니다. | 기본 문자열 검색과 별도인 SemanticMemoryProvider |
| MCP | AI 앱과 도구 서버가 기능 목록·호출을 주고받는 표준 프로토콜입니다. | MCP SDK와 packages/adapters/src/mcp-connector.ts |
| 계약 적합성 테스트 | 여러 구현이 같은 인터페이스의 행동 규칙을 지키는지 확인합니다. | packages/adapters/src/*-conformance.test.ts |
| 체크포인트 | 중단한 작업을 재개할 때 참고하는 저장 지점·상태입니다. | Run.checkpoint와 재개 경로 |
같은 단어라도 범위를 확인하세요. “활성 실행”과 “CPU를 쓰는 실행”, “기억”과 “벡터 검색”, “실시간”과 “기록의 영속성”, “컨테이너”와 “절대적인 보안 경계”는 각각 별도의 성질입니다. 타입·필드·조건문으로 그 차이를 확인하는 습관이 중요합니다.
외부 기술 문서는 독립적으로 갱신됩니다. 이 글의 버전 표는 저장소의 선언값이고, 공식 문서의 기본 화면은 다른 최신 버전을 설명할 수 있습니다. API를 따라 구현할 때에는 패키지 버전과 해당 문서 버전을 함께 확인하세요.
| 항목 | 내용 | 해석·기준 |
|---|---|---|
| 선정 | GitHub Trending TypeScript Weekly #8 | 2026-10-23 분석 기준 순위입니다. 순위와 관심 수치는 시간에 따라 달라집니다. |
| 스타 / 포크 | 5,309 / 946 | 2026-10-23 GitHub 기준 |
| 주 언어 | TypeScript | GitHub 메타데이터 기준 |
| 생성일 | 2026-08-13 | GitHub 저장소 생성일이며 개발 시작일을 뜻하지는 않습니다. |
| 최근 push | 2026-10-23 | GitHub 기준 날짜 |
| 최신 릴리스 | v0.3.3 · 2026-10-22 | apps/desktop/package.json도 0.3.3. 루트 패키지는 0.1.0으로 별개입니다. |
| 최근 커밋 | 8c65bc7 · 2026-10-23 | 제목: feat(mobile): open group settings from the group chat's title (#1932) |
| 제품 설명 | 오픈소스 Grok Bot 대안. 모델과 샌드박스를 직접 선택합니다. | GitHub 설명의 한국어 요약 |
| 성숙도 | 베타 | README의 명시적 상태. 기능 존재와 운영 안정성은 구분해야 합니다. |
| 라이선스 | Apache-2.0 | 저장소 라이선스. 연결하는 서비스의 별도 이용 조건까지 대체하지 않습니다. |
| 파일 / 패키지 | Git 추적 파일 2,795개 / 워크스페이스 패키지 20개 | 기준 커밋의 추적 파일 목록과 pnpm-workspace.yaml의 패키지 범위 기준 |
| TypeScript | .ts 1,788개·442,549줄 / .tsx 413개·103,563줄 | 합계 546,112줄. 테스트·빈 줄·주석 포함 |
| 테스트 분류 | 위 TypeScript 중 291,901줄 | .test. 파일명 또는 /e2e/ 경로 기준. 커버리지 수치가 아닙니다. |
| 웹 관련 | JavaScript 1,795줄 / CSS 5,935줄 / Astro 3,208줄 / HTML 580줄 | 확장자별 물리적 줄 수, 생성물 여부 별도 판별 없음 |
| 운영·저장 | Shell 3,443줄 / Python 2,564줄 / SQL 3,337줄 / Prisma 1,649줄 | 스크립트·마이그레이션·스키마를 포함한 집계 |
| 네이티브 | Kotlin 1,478줄 / Swift 117줄 / C 260줄 | 주 언어 외에도 플랫폼 연결 코드가 있습니다. |
줄 수는 git ls-files -z의 추적 파일 목록을 확장자별로 분류한 물리적 줄 수입니다. 마지막 개행이 없는 비어 있지 않은 파일도 마지막 한 줄을 포함합니다. JavaScript에는 .js·.mjs·.cjs를 합산하며, TypeScript에는 .d.ts가 포함됩니다. JSON·YAML·lockfile·Markdown·이미지는 위 언어별 집계에서 제외합니다.
집계한 표의 언어·형식만 합치면 570,478줄입니다. 이는 GitHub의 언어 비율 산정이나 주석을 뺀 유효 코드 줄 수와 다릅니다. 많은 테스트 코드가 있는 대형 제품이라는 독해 난이도의 참고값으로 사용하세요.
소스 기준: 설계·의존성·Dockerfile·CI 설정은 커밋 8c65bc7 기준입니다. 소스에 정의된 동작과 실제 배포 환경의 성능·제공자 연결 상태·CI 통과 결과는 구분해야 합니다.
설명과 구현의 차이: README의 자체 호스팅·다중 제공자 방향은 코드에 나타나지만 각 제공자의 기능·권한·설정은 다릅니다. 기본 기억 검색은 문자열 포함 검사이고 의미 검색은 별도 선택 경계입니다. 웹과 모바일의 언어·화면 기능도 완전히 같지 않습니다.
보안 경계: 승인 면제 도구가 존재합니다. 컨테이너와 신뢰된 로컬 컴퓨터는 격리 수준이 같지 않습니다. RLS는 앱 계정·정책 적용이 전제이며 시스템 문맥에는 명시적 우회가 있습니다. 저장 데이터 암호화는 서버가 내용을 전혀 읽지 못하는 종단간 암호화와 같은 주장이 아닙니다.
비용과 데이터: 외부 모델·앱·음성·컴퓨터 서비스를 고르면 해당 서비스로 데이터가 전송될 수 있고 비용·이용 조건이 적용됩니다. 로컬 모델을 선택해도 모델의 품질·도구 호출·시각 입력 지원을 별도로 확인해야 합니다.
시간과 추정: 스타·포크·릴리스는 2026-10-23 GitHub 기준입니다. 트렌딩 이유는 설계에 근거한 해석이며 유입 원인의 실증 분석이 아닙니다. 제시한 학습 시간과 권장 하드웨어는 교육용 추정입니다. 커밋 하나의 상태로 장기 변경 빈도·회귀율·인기 증가 추세를 판단할 수는 없습니다.