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

t3code 딥다이브
— 여러 AI 코딩 도구를 하나의 작업실로 묶는 법

앱·서버

빠른 판단

무엇
여러 코딩 에이전트에 공통 웹·데스크탑 제어 화면과 서버 작업 기록·Git 체크포인트를 제공하는 앱
이럴 때
한 작업 PC의 개발 환경을 여러 기기에서 제어하고 에이전트 실행·승인·코드 변경을 함께 추적할 때
내 컴퓨터에서 일하는 코딩 에이전트를 웹·데스크톱·휴대폰에서 제어하려면 무엇이 필요할까요? T3 Code의 TypeScript 소스를 따라가며 통신 규약, 이벤트 기록, 재연결, Git 체크포인트를 배웁니다. 화면 뒤의 Node.js 서버와 공용 패키지를 연결해서 읽는 초보자용 안내서입니다. 저장소 pingdotgg/t3code · 스타 29,528 · 포크 7,919 · MIT · 코드는 커밋 d7a8d6f3, 수치는 2026-10-23 GitHub 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

코딩 에이전트가 일하는 컴퓨터와, 그 일을 지켜보는 화면을 연결한다

T3 Code는 이미 설치하거나 연결해 둔 코딩 에이전트에 공통 화면을 제공하는 애플리케이션입니다. README는 이를 agent harness control surface라고 부릅니다. 여기서 harness는 에이전트의 도구 실행·승인·대화 진행을 관리하는 실행 틀이고, control surface는 사용자가 그 작업을 조작하는 화면입니다.

프로젝트 파일과 에이전트 실행은 서버가 있는 컴퓨터에 두고, 여러 기기의 화면이 같은 작업을 제어하도록 만드는 것이 핵심입니다. 데스크톱 앱은 Electron으로 웹 화면을 감싸고 서버 실행기도 함께 제공합니다. 모바일 앱은 React Native로 별도 구현되어 다른 컴퓨터의 서버에 접속합니다. 이 역할 구분은 docs/internals/overview.md의 소유권 경계와 서버·클라이언트 패키지 구성에서 확인할 수 있습니다.

한 문장 비유

여러 전문 작업자가 있는 공방에, 어디서든 연결되는 공용 작업대와 작업 일지를 붙입니다

Codex나 Claude Code는 실제로 일을 하는 작업자입니다. 서버는 작업자에게 요청을 전달하고 결과를 기록하는 공방 관리자입니다. 웹·데스크톱·휴대폰은 관리자의 장부를 함께 보며 지시하는 단말입니다.

휴대폰으로 수정 요청을 보내도 파일은 공방의 컴퓨터에서 바뀝니다. 이 때문에 화면이 잠깐 끊겨도 작업의 주인과 기록은 서버에 남아야 합니다. 이후 나오는 저장·재시도·동기화 설계가 모두 여기서 출발합니다.

1-1. 사용자 요청 하나가 지나가는 여섯 단계

  1. 사용자가 프로젝트와 에이전트를 고르고 요청을 입력합니다.
  2. 클라이언트가 규약에 맞춘 RPC 요청을 WebSocket으로 보냅니다.
  3. 서버가 권한과 현재 상태를 확인하고, 명령으로부터 이벤트와 후속 작업을 결정합니다.
  4. 이벤트·조회용 상태·명령 처리 영수증·후속 작업을 SQLite 트랜잭션에 저장합니다.
  5. 별도의 작업자가 공급자 어댑터를 통해 실제 에이전트를 실행하고 결과를 다시 상태에 반영합니다.
  6. 구독 중인 화면이 변경 사항을 받고, 실행 후 체크포인트로 코드 차이를 확인할 수 있습니다.
용어
공급자(provider) · 환경(environment) · 스레드(thread)
공급자는 Codex·Claude처럼 연결할 에이전트 실행 체계입니다. 환경은 서버 하나와 그 컴퓨터의 파일·인증·실행 상태를 묶은 단위입니다. 스레드는 프로젝트 안에서 이어지는 대화와 작업 이력입니다. 브라우저 탭을 닫았다고 서버의 스레드가 사라지는 모델이 아닙니다.

1-2. 처음 읽을 때 잡아야 할 범위

입문자는 모델의 추론 원리를 구현하는 프로젝트로 접근하기보다, 외부 프로그램을 안정적인 제품으로 연결하는 방법에 주목하면 좋습니다. packages/contracts의 데이터 규약, apps/server의 실행·저장, packages/client-runtime의 화면 공통 상태를 한 줄로 연결해 보는 것이 첫 목표입니다.

학습 목표
화면에서 출발해 저장소까지 왕복하기

“이 버튼이 어떤 요청을 보내며, 그 요청이 무엇으로 저장되고, 어떤 응답이 화면을 바꾸는가?”를 설명할 수 있으면 충분한 출발입니다. 전체 코드를 한 번에 이해할 필요는 없습니다.

2왜 주목받는가

월간 트렌딩이라는 결과와, 코드에서 읽을 수 있는 매력을 구분한다

2026-10-23 분석 기준 GitHub Trending Monthly 16위이며, 스타는 29,528개, 포크는 7,919개입니다. 이 숫자만으로 실제 사용자 수·성능·상승 원인을 알 수는 없습니다. 다음 세 가지는 README의 지향과 구현 구조를 함께 볼 때 추론할 수 있는 주목 요인입니다.

2-1. 기존 에이전트와 구독을 공통 화면으로 연결한다

README는 Claude Code·Codex·Cursor·Grok Build·OpenCode·Google Antigravity를 소개하며, 기존 구독과 연결하는 사용법을 설명합니다. 공급자마다 실행과 승인 방식이 다르지만, T3 Code는 공통 어댑터 규약으로 이를 받아들입니다. 공급자를 바꿀 때도 프로젝트·대화·차이 보기 같은 주변 경험을 이어 가려는 방향이 읽힙니다.

주목할 지점은 에이전트의 수보다, 서로 다른 에이전트를 하나의 상태 모델과 조작 방식으로 연결하는 설계입니다. 다만 실제 기능은 공급자별 역량에 따라 다릅니다. 공통 화면이 있다고 대화 복구·분기·실행 중 추가 지시까지 모두 같은 수준으로 지원되는 것은 아닙니다.

2-2. 원격 사용을 구조의 출발점에 둔다

웹·데스크톱·모바일이 서버를 공유하려면 인증, 끊긴 연결의 복원, 작은 데이터 전송이 필요합니다. apps/server/src/ws.ts의 subscribeOrchestrationV2Shell()은 마지막 수신 순서 번호와 재생 예산에 따라 변경분 재생이나 새 스냅샷을 선택합니다. packages/client-runtime은 연결과 상태 처리를 웹·모바일에서 공유합니다. 휴대폰 지원이 별도 화면 하나를 추가하는 문제를 넘어서는 이유입니다.

2-3. 경쟁 제품과 비교할 때 볼 축

README는 Codex 데스크톱 앱·Conductor·Claude Desktop·Cursor Glass에서 영감을 받았다고 밝힙니다. 아래는 이들 제품의 최신 기능 순위표가 아니라, 대안을 고를 때 확인할 설계 축입니다. 타 제품의 세부 기능이나 요금은 비교 범위에 포함하지 않습니다.

비교 축대안에서 확인할 점T3 Code에서 확인되는 선택
에이전트 선택주로 쓰는 공급자와 잘 맞는가공통 어댑터와 공급자별 패키지를 둔다
조작 화면터미널·편집기·데스크톱 중 어떤 흐름인가웹·Electron·React Native 화면을 제공한다
원격 작업파일과 실행 상태가 어느 기기에 남는가서버가 파일·에이전트를 소유하고 화면은 RPC로 제어한다
수정 가능성직접 고치거나 포크할 수 있는가핵심 소스와 빌드·릴리스 구성이 MIT로 공개되어 있다
운영 부담계정·업데이트·서버 관리를 누가 맡는가자체 서버와 공급자 인증, 클라이언트 호환성을 관리해야 한다

여러 에이전트를 같은 흐름으로 다루고 원격 화면까지 직접 확장하려는 개발자에게 구조적 장점이 있습니다. 반면 한 공급자의 고유 기능만 깊게 사용하는 경우에는 그 공급자의 전용 화면이 더 자연스러울 수도 있습니다. 범용성이 모든 작업에서 우위를 뜻하지는 않습니다.

2-4. README와 코드의 범위가 완전히 같지는 않다

문서와 구현 대조

여섯 공급자 소개보다 넓은 확장 코드

README의 주요 지원 목록은 여섯 공급자입니다. 하지만 apps/server/package.json에는 @t3tools/provider-pi와 @t3tools/provider-muse도 의존성으로 들어 있습니다. packages/provider-pi/src/server/adapter.ts에는 makePiAdapterV2와 PiAdapterV2Driver 구현이 있습니다.

docs/user/install.md의 공급자 표도 Pi와 Muse Code 설정을 설명합니다. 따라서 “README의 소개 목록”과 “해당 커밋에 들어 있는 구현 범위”를 구분해야 합니다. 패키지 존재만으로 모든 공급자의 기능 동등성이나 계정별 이용 가능성을 단정할 수는 없습니다.

해석의 한계
인기와 완성도는 서로 다른 지표다

README는 프로젝트가 초기 단계이며 버그가 있을 수 있다고 알립니다. 트렌딩 순위는 관심의 지표입니다. 실제 속도, 동시 실행 수, 장시간 안정성은 사용 환경에서 별도로 측정해야 합니다.

3기술 스택 전체 지도

package.json의 선언값과 공용 catalog를 연결해서 읽는다

이 저장소는 여러 앱과 패키지를 함께 관리하는 모노레포입니다. pnpm-workspace.yaml이 apps/*, infra/*, packages/*, scripts, oxlint-plugin-t3code를 워크스페이스로 묶습니다. 아래 버전은 커밋 d7a8d6f3의 의존성 선언 또는 catalog 값입니다. 개별 선언이 공용 overrides로 바뀌는 경우에는 그 차이도 표시했습니다.

용어
workspace:* · catalog: · 버전 범위
workspace:*는 같은 저장소의 패키지를 연결합니다. catalog:는 공용 버전 표를 참조합니다. ^0.3.276 같은 표기는 허용 범위이므로 정확히 그 버전만 설치한다는 뜻이 아닙니다. 설치 결과를 재현할 때는 pnpm-lock.yaml도 함께 읽어야 합니다.

3-1. 백엔드: 실행·통신·저장을 한 환경 안에 묶는다

부품실제 선언값·위치담당하는 일
Node.js루트 engines.node: 26.12.1개발 도구와 서버 실행 기반
서버 호환 범위apps/server/package.json: ^22.16 || ^23.11 || >=24.10배포 패키지의 허용 범위. 루트 개발 버전과 구분
Effecteffect, @effect/platform-node: 4.0.5 (catalog)비동기 작업, 서비스 주입, 스트림, 오류 모델
SQLitepackages/shared/src/nodeSqliteClient.ts가 node:sqlite 사용서버 이벤트·조회 상태·명령 영수증 저장
공급자 SDKClaude Agent SDK ^0.3.276, Cursor SDK 1.0.35, OpenCode SDK ^1.3.15각 공급자 실행 체계와 연결
터미널node-pty: ^1.2.0-beta.15 → 1.2.0-beta.15 (공용 overrides로 고정)가상 터미널에서 셸 프로세스 실행
토큰·HTTPjose: 6.2.2, undici: 8.11.2 (catalog)인증 토큰 처리와 HTTP 통신

SQLite 엔진 버전을 별도 npm SQLite 드라이버 버전으로 적으면 부정확합니다. 이 구현은 Node 내장 API를 사용합니다. apps/server/src/persistence/Sqlite.ts는 readerWorker: true와 checkpointWorker: true로 읽기 전용 작업과 체크포인트 동기화를 별도 워커에 맡기도록 설정합니다. 워커를 사용할 수 없는 경우에는 주 연결을 사용하는 대체 경로가 있습니다.

3-2. 프론트엔드: 같은 제품, 서로 다른 화면 기술

표면주요 기술·선언값소스에서 읽을 점
웹React·React DOM 19.3.0, TanStack Router ^1.160.2apps/web/package.json과 apps/web/vite.config.ts의 라우터 플러그인
입력·상태Tiptap ^3.31.3, Zustand ^5.0.11, @effect/atom-react: 4.0.5풍부한 입력기와 공용 런타임 상태를 나누는 방법
웹 스타일Tailwind CSS ^4.0.0 선언 → 공용 고정값 4.3.3pnpm-workspace.yaml의 overrides가 catalog를 적용하며 잠금 파일도 4.3.3을 기록
차이 보기@pierre/diffs: 1.5.2 (catalog)코드 검토 화면과 변경 파일 표현
데스크톱Electron 44.9.3, electron-builder 26.16.1웹 화면에 OS 연동과 서버 실행기를 결합
모바일Expo 58.0.14, React Native 0.88.0, Reanimated 4.7.1별도 네이티브 화면에서 공용 계약·상태 로직 재사용

웹과 모바일은 HTML 컴포넌트를 그대로 공유하는 구조가 아닙니다. 두 앱이 함께 의존하는 @t3tools/client-runtime과 @t3tools/contracts가 공통 동작을 담당합니다. 화면이 달라도 재연결 규칙을 따로 구현하지 않게 하는 선택입니다.

3-3. 인프라·빌드: 로컬 환경과 원격 제어 기반을 구분한다

영역선언값·설정역할
패키지 관리pnpm@12.14.0루트 packageManager에 명시
빌드·검사vite-plus: 1.3.0vp 명령으로 빌드·테스트·정적 검사 구성
Vite·TypeScript 이름vite → @voidzero-dev/vite-plus-core@1.3.0
typescript → @tsc-rs/typescript@0.8.0
catalog에서 npm 별칭 사용. 이름만 보고 일반 배포판 버전을 추정하지 않는다
브라우저 테스트Playwright 1.60.0, @vitest/browser-playwright: 5.0.5웹 설정이 단위 테스트와 Chromium 테스트를 분리
T3 ConnectCloudflare Worker, Alchemy 2.0.0-beta.80, Drizzle 1.0.0-rc.5-ab785fcinfra/relay에서 환경 연결·단기 자격증명·알림 관리
네이티브 보조 도구Rust edition 2024, sysinfo 0.39.3, serde 1.0.228native/resource-monitor/Cargo.toml: 시스템 자원 측정

infra/relay/src/worker.ts는 Cloudflare Worker와 Postgres 관련 계층을 조합합니다. infra/relay/README.md는 연결된 뒤 일반 API·WebSocket 통신이 선택한 환경으로 향한다고 설명합니다. 이 제어 평면을 로컬 SQLite 서버의 저장 계층과 혼동하면 전체 구조를 잘못 이해하게 됩니다.

의존성 목록을 읽는 목표는 라이브러리 이름 암기가 아니라, 각 부품이 어느 경계와 책임을 맡는지 설명하는 것입니다.

3-4. CI 설정도 아키텍처의 일부다

.github/workflows/ci.yml의 changes 작업은 패키지 영향 범위를 계산합니다. 린트는 문서를 포함해 항상 실행하고, 빌드·타입 검사·서버·웹·모바일 테스트 등은 영향 범위에 따라 실행합니다. 변경 감지에 실패하면 검사를 넓게 실행하는 방향으로 설계되어 있습니다.

비유

모노레포는 한 건물에 여러 작업실이 있는 형태입니다. 공용 전기 배선(contracts)을 고치면 여러 방을 점검해야 하지만, 한 방의 안내문만 바꾸면 모든 기계를 다시 돌릴 필요는 없습니다. CI의 의존성 그래프가 그 점검 범위를 결정합니다.

4아키텍처 심화 분석

하나의 요청을 네 가지 설계 패턴으로 해부한다

4-0. 전체 시스템 구조도

아래 그림의 핵심 경계는 조작하는 기기와 파일을 소유한 실행 환경 사이입니다. Electron의 렌더러도 이 규약을 따릅니다. T3 Connect의 연결 제어와 실제 에이전트 실행은 서로 다른 책임입니다.

[사용자의 화면] apps/web apps/desktop apps/mobile React Electron + 웹 React Native │ │ │ └──────────────────┴────────────────────┘ │ packages/client-runtime 공통 연결·상태 packages/contracts 입력·출력 스키마 │ 인증된 HTTP 요청 / WebSocket RPC │ [프로젝트를 소유한 실행 환경] apps/server/src/ws.ts + auth/RpcAuthorization.ts │ Orchestrator.ts 명령 처리·이벤트 결정 │ EventSink.ts 하나의 SQLite 트랜잭션 ┌───────────┼───────────┬─────────────┐ │ │ │ │ 이벤트 로그 조회용 상태 명령 영수증 effect outbox │ │ │ └───── 커밋 뒤 화면에 알림 EffectWorker.ts │ 공급자 어댑터 / Git / 셸 │ 실제 에이전트·프로젝트 파일 │ 결과를 다시 명령·이벤트로 반영 [선택적 원격 연결 제어] infra/relay 계정 연결·환경 발견·단기 자격증명·알림 └── 연결 가능한 환경의 엔드포인트를 클라이언트에 안내

4-1. 패턴 ①: 계약과 어댑터로 변경의 전파를 제한한다

packages/contracts/src/rpc.ts의 WsOrchestrationV2SubscribeShellRpc는 요청 입력, 성공 응답, 오류를 함께 선언합니다. TypeScript 타입을 공유하는 것에 더해 Effect Schema로 통신 경계의 데이터 모양을 표현합니다. 잘못된 JSON을 단순 타입 단언으로 믿는 방식과 구별되는 지점입니다.

// packages/contracts/src/rpc.ts의 선언
const WsOrchestrationV2SubscribeShellRpc = Rpc.make(
  ORCHESTRATION_V2_WS_METHODS.subscribeShell,
  {
    payload: OrchestrationV2RpcSchemas.subscribeShell.input,
    success: OrchestrationV2RpcSchemas.subscribeShell.output,
    error: Schema.Union([
      OrchestrationV2GetShellSnapshotError,
      EnvironmentAuthorizationError,
    ]),
    stream: true,
  },
);

반대쪽 경계에는 공급자가 있습니다. packages/provider-core/src/server/ProviderAdapter.ts의 ProviderAdapterV2는 getCapabilities()·planSelectionTransition()·openSession()을 노출합니다. 열린 세션의 ProviderAdapterV2SessionRuntime에는 startTurn(), interruptTurn(), rollbackThread(), 이벤트 스트림 등이 정의됩니다.

구체적인 예는 packages/provider-pi/src/server/adapter.ts의 makePiAdapterV2()와 PiAdapterV2Driver.create()입니다. Pi 고유의 프로토콜 처리는 이 어댑터 안에 두고, 상위 계층에는 ProviderAdapterV2Event의 message.updated·turn.terminal 같은 공통 사건을 전달합니다.

비유

통신 계약은 주문서 양식이고, 어댑터는 주방마다 다른 표현을 공용 양식으로 옮기는 통역사입니다. 새 주방이 생겼다고 계산대와 배달 앱 전체가 그 주방의 사투리를 배울 필요는 없습니다.

인증과 권한도 따로 읽어야 합니다. apps/server/src/auth/RpcAuthorization.ts의 RPC_REQUIRED_SCOPES는 목록 구독에 읽기 권한을, 명령 실행에 조작 권한을 지정합니다. 소켓에 접속할 수 있다는 사실과 모든 메서드를 실행할 수 있다는 사실은 다릅니다.

4-2. 패턴 ②: 이벤트 기록과 outbox를 함께 커밋한다

apps/server/src/orchestration-v2/Orchestrator.ts의 dispatchWithReceipt()는 스레드별 잠금을 적용합니다. 내부 dispatchWithReceiptEffect()는 같은 commandId의 영수증을 먼저 조회합니다. 이미 처리한 명령이면 기존 결과를 돌려주고, 다른 스레드에 같은 ID를 재사용한 충돌도 검사합니다.

새 명령은 현재 상태에 따라 이벤트와 후속 작업의 계획을 만듭니다. 이 단계에서 공급자 실행이나 프로젝트 파일 변경을 직접 수행하지 않습니다. 다만 저장된 상태를 조회하므로, 함수 전체가 데이터베이스와 무관한 순수 함수라고 부르는 것도 정확하지 않습니다.

// EventSink.ts의 commitCommandEffect() 내부 핵심 순서
// 예약·중복 처리·오류 처리를 생략한 발췌
const storedEvents = yield* eventStore.append({
  commandId: input.commandId,
  events: normalized,
});
const outcomes = yield* applyStoredEvents(storedEvents);
yield* effectOutbox.enqueue(input.effects);
// 이어서 결과 sequence를 담은 receipt를 upsert한다.
// 이 작업 전체를 commitThenPublish()의 withTransaction이 감싼다.

EventSink.ts의 commitCommandEffect()는 영수증 예약, 이벤트 추가, 조회용 상태 반영, outbox 등록, 최종 영수증 기록을 같은 트랜잭션에 넣습니다. commitThenPublish()는 커밋 후에 알림을 발행하며, 발행 순서를 위한 세마포어도 사용합니다. 화면에 보인 결과가 아직 저장되지 않은 상태가 되는 일을 줄이는 설계입니다.

용어
이벤트 소싱 · CQRS · 트랜잭셔널 outbox
이벤트 소싱은 일어난 사건을 상태의 근거로 남기는 방식입니다. CQRS는 변경을 결정하는 경로와 화면이 읽기 좋은 조회 상태를 나누는 방식입니다. outbox는 “저장한 뒤 해야 할 외부 작업”도 같은 데이터베이스에 기록해, 의도만 저장되고 실행 요청은 잃어버리는 틈을 줄입니다.
끊기는 시점이 구조에서 남는 것학습할 질문
커밋 이전트랜잭션이 성공하지 않았다면 변경은 수락되지 않음재전송할 명령 ID를 유지하는가?
커밋 후 응답 이전영수증·이벤트·후속 작업이 함께 남음같은 명령을 두 번 처리하지 않는가?
외부 작업 실행 중outbox 상태와 실행 시도 정보가 남음외부 동작을 재시도해도 안전한가?
작업 완료 후 연결 단절서버의 저장 상태와 이벤트가 남음화면은 어느 순서 번호부터 복구하는가?

EffectWorker.ts의 layerWithOptions()는 outbox.claimNext()로 작업을 확보하고, executor.execute()를 실행한 뒤 성공·재시도·실패를 기록합니다. 실행 직전 취소 상태를 다시 확인하고 실행과 취소 신호를 경쟁시키는 코드도 있습니다. runDaemonWithOptions()의 기본 작업자 동시성은 4입니다. 이는 에이전트 네 개만 실행할 수 있다는 제품 한도가 아니라 후속 작업 처리기의 기본 설정입니다.

서버가 요청을 수락했다는 응답은 의도가 저장됐다는 뜻이며, 에이전트 실행과 체크포인트까지 끝났다는 뜻은 아닙니다. 이 차이를 화면의 진행 상태와 테스트에서도 유지해야 합니다.

설계의 한계
outbox가 모든 외부 실행을 정확히 한 번으로 만들지는 않는다

외부 작업이 성공한 직후 완료 표시 전에 프로세스가 종료되면, 저장소만 보고 성공 여부를 판단하기 어려울 수 있습니다. 명령 중복 방지와 외부 부작용의 중복 방지는 별개입니다. 공급자 프로세스를 잃은 작업의 복구 정책도 따로 필요합니다.

4-3. 패턴 ③: 전체 상태와 변경분 사이에 예산을 둔다

이 코드에서 shell은 문맥에 따라 뜻이 달라집니다. ShellStream.ts의 shell은 프로젝트·스레드 목록을 그릴 최소 요약입니다. 명령을 입력하는 터미널 셸과 구별해야 합니다. 전체 대화 본문은 별도 스레드 구독에서 다룹니다.

apps/server/src/ws.ts의 subscribeOrchestrationV2Shell()은 처음 접속하면 loadSnapshot()을 호출합니다. 재접속하면 먼저 latestApplicationSequence로 현재 상한을 읽고, 마지막 수신 번호부터 재생 가능한 범위인지 확인합니다. 가능하면 loadProjectMetadataSnapshot()으로 작은 메타데이터를 읽고, 해당 범위의 사건만 따라갑니다.

처음 접속 전체 활성 목록 스냅샷 ──▶ 필요하면 synchronized ──▶ 실시간 변경 재접속: 클라이언트가 afterSequence = 120을 기억 현재 highWater = 125 확인 │ ├─ 재생 가능한 범위 │ 프로젝트 메타데이터 ──▶ 121~125 범위 재생 ──▶ 실시간 변경 │ └─ 범위를 복구할 수 없음 새 목록 스냅샷 ──▶ 실시간 변경 요청 시 재생·스냅샷 뒤에 synchronized 표식을 보내고 실시간 변경으로 연결 주요 구분: 목록 요약 / 선택한 대화 내용 / 터미널 출력은 별도 흐름

여기서 highWater는 “과거분을 어디까지 재생할지” 고정하는 경계입니다. 재생하는 동안 새 사건이 생겨도 과거분의 끝을 계속 옮기지 않고, 정해 둔 상한 뒤를 실시간 흐름으로 이어 붙입니다.

제한설정값실제 위치·의미
목록 재개 간격SHELL_RESUME_MAX_GAP = 1_000ws.ts: 재생 가능 범위 판단에 사용
목록 재생 원문 예산8 MiBws.ts의 canReplayPersistedRange()가 원문 payload 크기도 검사
대화 재생 사건 수128ThreadStream.ts: 초과하면 스냅샷 선택
대화 재생 데이터인코딩 결과 1,048,576바이트decideThreadResume(): 사건 수와 별도로 제한
대화 재생 원문 예산1,048,576바이트ThreadStream.ts의 isThreadReplayRawPayloadSafe(): 디코딩 전 비용 판단
실시간 보관 예산1,000항목 / 직렬화 데이터 8 MiBLiveStreamBudget.ts의 기본 제한
목록 변경 묶음최대 512건 또는 50 msws.ts의 Stream.groupedWithin()

ShellStream.ts의 coalesceShellApplicationEvents()는 같은 프로젝트·스레드의 여러 사건을 최신 것 중심으로 묶습니다. bufferShellLiveStream()은 동일 요약 상태의 대기 중 갱신을 합칩니다. 반면 스냅샷·동기화 표식까지 같은 방식으로 버리면 순서 의미가 깨지므로 구분합니다.

커밋 제목도 이 학습 주제와 연결됩니다. d7a8d6f3의 제목은 fix(server): avoid full shell read on resume (#23987)입니다. 현재 소스에는 재개 경로가 전체 목록 로드를 우회하는 분기가 있으며, apps/server/src/ws.test.ts에는 알 수 없는 사건을 건너뛰고 동기화 뒤 실시간 사건을 받는 테스트가 있습니다. 성능 개선율을 수치로 주장할 근거는 이 코드만으로는 없습니다.

4-4. 패턴 ④: Git 객체로 작업 전후 상태를 남긴다

apps/server/src/checkpointing/Utils.ts의 checkpointRefForThreadTurn()은 refs/t3/checkpoints/…/turn/… 형태의 ref를 만듭니다. CheckpointStore.ts의 captureCheckpoint()는 현재 VCS 드라이버의 체크포인트 기능을 찾아 위임합니다.

실제 Git 구현은 apps/server/src/vcs/GitVcsDriver.ts의 GitVcsDriver.checkpoints.captureCheckpoint에 있습니다. 임시 인덱스를 만들고 GIT_INDEX_FILE로 지정한 뒤, write-tree·commit-tree·update-ref를 사용합니다. 체크포인트 커밋 객체는 만들지만 사용자의 브랜치를 앞으로 이동시키지 않는 방식입니다.

작업 디렉터리의 상태 │ ▼ 별도 임시 인덱스 (GIT_INDEX_FILE) │ write-tree ▼ 트리 객체 ── commit-tree ──▶ 체크포인트 커밋 객체 │ update-ref ▼ refs/t3/checkpoints/… 사용자의 브랜치 HEAD와 스테이징 인덱스는 별도 경로로 유지

초보자에게 좋은 발견은 Git이 명령 모음인 동시에 파일 상태를 저장하는 객체 데이터베이스라는 점입니다. Git 공식 문서의 객체 설명을 함께 읽으면 트리와 커밋의 관계를 이해하기 쉽습니다. 이 저장소에서는 그 기능을 제품의 차이 보기와 되돌리기에 이용합니다. 단, 파일을 되돌리는 것과 에이전트가 기억하는 대화를 되돌리는 것은 함께 조정해야 합니다. 내부 아키텍처 문서는 공급자가 대화 롤백을 지원하지 않으면 파일 변경 전에 거부해야 한다고 설명합니다.

코드 읽기 질문
네 가지 패턴을 관통하는 질문은 “누가 진실을 소유하는가?”다

규약은 경계의 모양을, 서버 로그는 작업 이력을, 조회 상태는 화면용 결과를, Git 객체는 파일 상태를 맡습니다. 저장소가 여러 개인 것보다 각 저장소가 답하는 질문이 다르다는 점을 이해하는 것이 중요합니다.

5디렉토리 구조 해부

폴더 이름보다 앱·공통 규약·실행 어댑터의 방향을 먼저 읽는다
t3code/ ├── apps/ │ ├── server/ │ │ └── src/ │ │ ├── ws.ts RPC와 구독 진입점 │ │ ├── auth/ 환경 권한 검사 │ │ ├── orchestration-v2/ 명령·이벤트·후속 작업 │ │ ├── persistence/ SQLite와 마이그레이션 │ │ ├── provider/ 공급자 통합과 상태 탐지 │ │ ├── checkpointing/ 체크포인트 서비스 │ │ ├── vcs/ Git 등 VCS 구현 │ │ └── git/ 소스 관리 작업 조합 │ ├── web/ React 웹 화면 │ ├── desktop/ Electron 셸·OS 연동 │ ├── mobile/ Expo·React Native 앱 │ └── marketing/ 소개 사이트 ├── packages/ │ ├── contracts/ 전송·도메인 스키마 │ ├── client-runtime/ 공용 연결·상태·작업 로직 │ ├── shared/ 공통 실행 유틸리티 │ ├── provider-core/ 공급자 공통 규약 │ ├── provider-*/ 개별 공급자 어댑터 │ ├── effect-codex-app-server/ Codex 프로토콜 연동 │ ├── effect-acp/ ACP 프로토콜 연동 │ ├── source-control-*/ 호스팅 서비스별 연동 │ ├── ssh/ SSH 연결 지원 │ └── tailscale/ Tailscale 연결 지원 ├── infra/relay/ T3 Connect 제어 평면 ├── native/ 자원 측정·화면 캡처 등 ├── scripts/ 개발·패키징·릴리스 도구 ├── oxlint-plugin-t3code/ 저장소 전용 린트 규칙 ├── docs/ 사용자·내부 구조·운영 문서 ├── .github/workflows/ CI·배포·릴리스 ├── .repos/ 외부 프로젝트 참조 코드 ├── pnpm-workspace.yaml 패키지 경계·공용 버전 └── pnpm-lock.yaml 의존성 해석 결과

5-1. 의존성은 아래에서 위로 쌓아 읽는다

읽기 층대표 경로확인할 질문
규약packages/contracts/src/orchestrationV2.ts스레드·실행·이벤트의 모양은 무엇인가?
공통 동작packages/client-runtime/src/state/shellReducer.ts변경 사건이 어떤 화면 상태로 바뀌는가?
서버 진입점apps/server/src/ws.ts요청이 어느 서비스로 연결되는가?
결정과 저장apps/server/src/orchestration-v2/Orchestrator.ts·apps/server/src/orchestration-v2/EventSink.ts어떤 의도를 어떻게 원자적으로 남기는가?
외부 실행packages/provider-*/src/server/외부 프로토콜을 공통 사건으로 어떻게 옮기는가?

폴더 이름이 비슷해도 역할은 다릅니다. packages/source-control-github 등은 호스팅 서비스와의 연동을 담당하고, apps/server/src/vcs는 작업 디렉터리의 버전 관리 연산을 담당합니다. GitHub API에서 PR을 읽는 일과 로컬 Git 인덱스를 다루는 일을 같은 책임으로 묶지 않습니다.

공급자 구현도 모두 packages/provider-*로 이동한 것은 아닙니다. Codex·Claude 관련 구현은 apps/server/src/provider에도 많이 남아 있습니다. 디렉토리의 대칭성을 가정하지 말고 실제 import와 서비스 연결을 따라가야 합니다.

5-2. 코드 규모와 집계 범위

집계 범위는 Git 추적 파일 중 apps·packages·infra·native·scripts·oxlint-plugin-t3code입니다. 경로 구성요소가 Vendor, vendor, deps, node_modules, dist, dist-electron, build인 파일은 제외했습니다. .repos의 참조 코드는 포함하지 않았습니다.

언어·확장자파일 수물리 줄 수
TypeScript4,303개 (.ts·.mts)1,191,488줄
TSX1,035개256,589줄
JavaScript53개 (.js·.mjs·.cjs)5,456줄
Rust10개4,237줄
Swift / Kotlin21개 / 27개7,010줄 / 7,120줄
C・C++・헤더41개 (.c·.cpp·.h)9,699줄
CSS6개5,974줄
기타 집계 확장자23개2,102줄
합계5,519개1,489,675줄

기타에는 .m·.mm·.py·.sh·.ps1·.qml·.zig를 포함했습니다. 공백·주석·테스트·생성 코드를 포함하는 물리 줄 수이며, 외부 유래 헤더까지 모두 제거한 순수 수작업 구현량은 아닙니다. JSON·YAML·Markdown·바이너리는 합계에서 제외했습니다.

이 범위의 TypeScript·TSX 테스트 파일은 .test.·.spec. 이름 기준 1,485개, 466,623줄입니다. TypeScript 계열 약 145만 줄의 약 32%지만, 테스트 비중만으로 검증 품질을 단정하지는 않습니다. 규모가 큰 이유 중 하나는 packages/effect-codex-app-server처럼 상세한 프로토콜 스키마가 코드로 들어 있기 때문입니다.

추천 읽기 순서
가장 큰 파일보다 가장 작은 규칙부터

ThreadStream.ts의 재개 판단 → shellReducer.ts의 상태 갱신 → rpc.ts의 구독 계약 → ws.ts의 실제 연결 순서로 읽어 보세요. 작은 규칙을 알고 큰 파일에 들어가면 조건문의 목적이 보입니다.

6학습 포인트 (기술별)

기술을 배우고, 이 저장소에서 쓰인 이유를 확인하고, 작은 모형으로 옮긴다

6-1. TypeScript: 종류에 따라 데이터가 달라지는 타입

ProviderAdapterV2Event는 type으로 사건 종류를 구분하는 유니온입니다. 메시지 갱신과 명령 출력 조각은 필요한 필드가 다릅니다. 모든 필드를 선택 사항으로 만드는 대신 종류별 모양을 나누면, 분기 안에서 필요한 값이 존재한다는 것을 타입으로 설명할 수 있습니다.

// 학습용 축소 모형. 실제 계약의 필드와는 다르다.
type Update =
  | { type: "message.updated"; text: string }
  | { type: "turn.terminal"; status: "completed" | "failed" };

function summarize(update: Update): string {
  switch (update.type) {
    case "message.updated":
      return update.text;
    case "turn.terminal":
      return update.status;
  }
}

실습 아이디어: 새 사건 종류를 추가하고 처리하지 않은 분기를 찾아보세요. 이어서 실제 packages/provider-core/src/server/ProviderAdapter.ts에서 command_output.delta와 turn.terminal의 필드를 비교합니다. 타입만 맞는 데이터와 실행 중 검증을 통과한 데이터의 차이도 설명해 봅니다.

6-2. Effect: 작업의 결과·실패·필요 서비스를 읽는다

Effect 코드의 낯선 기호부터 줄여 봅시다. Effect.Effect<A, E, R>에서 A는 성공값, E는 예상 가능한 실패, R은 필요한 서비스입니다. yield*는 이 작업을 조합하는 문맥에서 값을 받는 표현이며, Layer는 서비스와 그 의존성을 구성합니다. 세 타입 매개변수의 의미는 Effect 4 공식 타입 문서에서도 확인할 수 있습니다.

apps/server/src/checkpointing/CheckpointStore.ts의 make는 VcsDriverRegistry를 받아 체크포인트 서비스를 만듭니다. 마지막의 Layer.effect(CheckpointStore, make)는 “이 서비스는 이 구성 작업으로 만든다”는 연결입니다. 단순 유틸리티 함수와 외부 자원을 사용하는 서비스를 구분해서 읽어 보세요.

용어
의존성 주입과 Scope
의존성 주입은 함수가 필요한 데이터베이스나 공급자를 직접 고정하지 않고 바깥에서 받도록 만드는 방식입니다. Scope는 프로세스·구독 같은 자원을 언제 정리할지 묶는 수명 범위입니다. 테스트에서 가짜 공급자를 넣거나, 연결 종료 때 자원을 정리하는 데 유용합니다.

실습 아이디어: 체크포인트 서비스 선언의 각 메서드에서 성공값과 실패 타입을 표로 옮깁니다. Effect.Effect<boolean, CheckpointStoreError>에서 false와 오류가 서로 다른 상태인 이유를 설명해 보세요. 없는 ref를 찾는 일과 Git 실행 자체가 실패한 일은 같지 않습니다.

6-3. React 상태: 값이 같으면 기존 객체를 살린다

packages/client-runtime/src/state/shellReducer.ts의 reuseUnchangedThreadShells()는 이전 스레드를 ID로 찾고, 의미상 같으면 기존 객체를 재사용합니다. applyShellStreamEvent()는 같은 요약이 와도 수신 순서 번호를 갱신할 수 있습니다. 화면에 보이는 값의 변화와 동기화 진행을 분리한 사례입니다.

mergeShellSnapshotProjects()는 프로젝트 메타데이터 보강 응답과 전체 상태를 확정하는 응답도 구분합니다. 작은 보강 응답의 빈 스레드 배열을 전체 상태로 오해해 목록을 지우면 안 됩니다. “응답 모양이 비슷하다”는 이유만으로 같은 병합 규칙을 적용하지 않는 것이 핵심입니다.

실습 아이디어: 이름이 같은 스레드 객체가 다시 왔을 때 객체 참조를 유지하는 경우와 새로 만드는 경우를 그림으로 비교합니다. 이후 렌더링 횟수를 측정할 수 있는 작은 별도 예제로 옮겨 보세요. 객체를 재사용한다는 사실만으로 모든 React 렌더링이 사라진다고 단정하지는 않습니다.

6-4. 데이터베이스: 디스크의 안전성과 응답성을 함께 본다

apps/server/src/persistence/Sqlite.ts는 journal_mode = WAL, synchronous = NORMAL, busy_timeout = 5000, foreign_keys = ON을 설정합니다. WAL은 변경 기록을 별도 로그에 쌓는 방식이고, busy timeout은 잠긴 데이터베이스를 만났을 때 기다리는 시간을 지정합니다.

같은 파일은 WAL 재사용 시 남길 파일 크기의 기준을 32 * 1024 * 1024바이트로 지정합니다. 설정 이름은 journal_size_limit이며, 실행 중 WAL이 절대 넘지 못하는 상한은 아닙니다. 체크포인트 디스크 동기화는 별도 워커에 맡기도록 설정합니다. 여기서 SQLite 체크포인트는 앞 절의 Git 체크포인트와 다른 개념입니다. SQLite에서는 WAL 내용을 주 데이터베이스에 반영하는 작업을 뜻합니다.

절충 이해하기
NORMAL 설정이 모든 장애에서 최근 커밋 보존을 뜻하지는 않는다

소스 주석은 매 커밋의 동기화가 이벤트 루프를 막는 문제를 설명합니다. 선택한 설정에서는 운영체제 중단이나 전원 손실 때 마지막 체크포인트 이후 커밋이 되돌아갈 수 있습니다. 응답성과 내구성을 어떤 장애 모델에서 맞바꾸는지 읽어야 합니다. SQLite WAL 공식 문서도 NORMAL 설정에서의 동기화와 전원 손실 절충을 설명합니다.

실습 아이디어: “앱 종료”, “운영체제 중단”, “네트워크 단절”을 서로 다른 열로 놓고 어떤 데이터가 이미 디스크에 있는지 표시합니다. 실제 데이터베이스를 열거나 변경하지 않아도 트랜잭션과 동기화의 차이를 연습할 수 있습니다.

6-5. 네트워크: 느린 소비자도 시스템의 일부다

LiveStreamBudget.ts는 보관 중인 항목 수와 직렬화 바이트를 따로 셉니다. 큰 사건 몇 개와 작은 사건 수천 개는 서로 다른 부담이기 때문입니다. ShellStream.ts의 composeShellStreamWithEnrichment()는 초기 응답이 메타데이터 보강 응답보다 먼저 나가도록 흐름을 구성합니다.

실습 아이디어: 서버가 100개의 갱신을 만들고 화면이 10개만 읽는 상황을 그려 보세요. 같은 대상의 전체 상태 갱신은 최신 것으로 합칠 수 있지만, 모든 종류의 사건을 버려도 되는 것은 아닙니다. 어떤 사건에 합치기를 허용할지 먼저 분류합니다.

6-6. 테스트: 시간을 기다리지 말고 완료 조건을 기다린다

packages/shared/src/DrainableWorker.ts의 makeDrainableWorker()는 대기열뿐 아니라 처리 중인 작업을 포함한 outstanding을 관리합니다. drain은 이 값이 0이 되는 것을 기다립니다. 대기열에서 꺼냈지만 아직 끝나지 않은 작업을 놓치지 않는 설계입니다.

apps/server/src/ws.test.ts는 재연결 중 알 수 없는 미래 사건이 있어도 이후 정상 사건과 실시간 꼬리를 받는 경우를 다룹니다. 이런 테스트는 성공 화면만 확인하는 것보다 프로토콜 호환성의 경계를 잘 드러냅니다.

비동기 테스트의 완료 기준은 몇 초가 지났는지가 아니라, 필요한 사건과 작업이 실제로 끝났는지여야 합니다.

실습 아이디어: “대기열이 비었다”와 “작업이 완료됐다”가 다른 상황을 두 단계 도식으로 작성합니다. 이후 별도 연습 예제에서는 Deferred 신호나 drain을 완료 조건으로 사용해 봅니다.

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

2026-10-31 기능 개정: 고정 커밋 79f1fadfc602과 v0.1.6 릴리스에서 사이드 채팅, 프로젝트 고정·보관, 1Password 자격증명 연결을 확인했다. 사이드 채팅은 lineage.sideChat와 parentThreadId로 부모 스레드를 검증한 뒤 별도 탭을 연다. 도구 diff와 셸 실행은 부모의 diff 패널·터미널로 연결하므로 대화 분리와 작업 환경 분리는 구별해야 한다.

1Password 드라이버는 op CLI의 read와 inject를 호출하며 읽기를 45초로 제한한다. 여러 참조는 한 번에 읽고, 제한시간이 끝나면 참조별 재호출로 대기를 늘리지 않는다. 프로젝트 고정·보관 시각은 SQLite projection의 pinned_at·archived_at 열에 남긴다. 실습에서는 보관과 파일 삭제가 어떻게 구분되는지 확인하고, 1Password는 별도 CLI 설치·인증을 먼저 준비한다.

코드 근거: 사이드 채팅 패널 연결 · 1Password 읽기 드라이버 · 프로젝트 상태 마이그레이션 · v0.1.6 릴리스.

2026-10-31 추가 소스 검토: 고정 커밋 d2d1e4f에서 데스크톱 원격 노출 상태는 현재 네트워크 인터페이스를 다시 읽어 광고 주소를 계산합니다. Wi-Fi·DHCP 주소가 바뀌어도 오래된 접속 주소를 계속 보여 주지 않도록 한 변화입니다. 네트워크 접근 모드의 바인딩과 사용자에게 안내하는 endpoint를 구분해서 읽습니다.

Clerk 로그인 저장소는 전용 디렉터리 0700·토큰 파일 0600 권한을 적용하고, 옛 파일을 이관하며, 심볼릭 링크·일반 파일 여부를 검사합니다. 읽기·쓰기·삭제 작업 뒤에도 파일 권한을 다시 좁힙니다. 이는 파일 접근 권한 관리이며 암호화를 보장한다는 뜻은 아닙니다. Linux 테마는 XDG portal의 변경 신호를 따라가고 portal이 없으면 Chromium의 선택을 유지합니다.

2026-10-30 소스 대조: 고정 커밋 32ecd71의 서버 package.json은 0.1.5입니다. 아래 Node 허용 범위와 루트 Node·pnpm 조건은 여전히 일치합니다. 초판의 0.1.2·스타·포크는 당시 기록으로 남깁니다. 현재 README는 Codex·Claude·Cursor·Grok Build·OpenCode·Antigravity 경로를 안내하며 스레드를 시작하기 전에 최소 한 provider의 설치·인증이 필요합니다.

최신 내부 문서는 설정 소유권을 더 명확히 구분합니다. 클라이언트 선호는 해당 클라이언트에, 환경 기본값과 프로젝트 덮어쓰기는 작업 서버에 있습니다. 사용할 수 없는 설정 대상에서 다른 환경으로 자동 대체하지 않으며, All environments는 현재 연결되고 로드된 서버에 대한 명시적 일괄 수정입니다. 오프라인·미래 환경까지 동기화하는 전역 기본값으로 해석하면 안 됩니다. 원격 단말이 자기 파일·인증 정보를 서버 상태 대신 쓰지 않는다는 기존 실행 경계도 유지됩니다.

화면만 쓰는 기기, 에이전트가 일하는 호스트, 제품을 빌드하는 환경은 다르다

7-1. 소스와 문서에 명시된 조건

대상확인되는 조건근거
에이전트 호스트사용할 공급자 설치 또는 관리형 런타임 연결과 인증 필요docs/user/install.md
서버 실행패키지 Node 범위 ^22.16 || ^23.11 || >=24.10apps/server/package.json
개발 도구루트 Node 26.12.1, pnpm 12.14.0, Vite+ 1.3.0루트 manifest와 catalog
모바일 빌드 설정iOS deploymentTarget: 18.0, Android minSdkVersion: 24apps/mobile/app.config.ts. 배포 스토어의 요구사항과는 별도
Intel Mac 서버설치 문서는 독립 t3 실행 파일이 없으며 소스 빌드를 안내docs/user/install.md. 데스크톱 앱은 별도로 안내
원격 화면에이전트 호스트가 켜져 있고 접속 가능해야 함docs/user/remote-access.md

배포 실행 파일이나 Electron 앱을 쓰는 경우와, Node로 소스를 직접 실행하는 경우의 준비물이 다릅니다. 또한 공급자 자체의 요구사항이 추가될 수 있습니다. Node 허용 범위를 만족한다는 것만으로 공급자·네이티브 모듈까지 검증됐다는 뜻은 아닙니다.

7-2. 학습용 권장 사양: 공식 최소 사양은 아니다

아래는 이 구조를 공부하기 위한 보수적인 출발점입니다. 저장소에서 보장하는 최소 RAM·CPU·디스크 수치가 아니며, 에이전트 수·프로젝트 크기·브라우저 테스트·시뮬레이터에 따라 달라집니다.

학습 범위권장 출발점비용이 늘어나는 지점
코드 읽기RAM 8 GB급 일반 개발용 컴퓨터와 편집기참조 코드까지 포함한 전체 검색·대형 파일 인덱싱
서버·웹 개발CPU 4코어, RAM 16 GB, SSD 여유 15~20 GB 정도의존성 캐시, 여러 에이전트, 브라우저·빌드 동시 실행
모바일 개발RAM 24~32 GB 정도면 시뮬레이터 병행에 여유플랫폼 SDK·네이티브 빌드·시뮬레이터 저장 공간
원격 관찰호환되는 브라우저 또는 모바일 앱실제 코드 작업 부하는 접속 대상 호스트에 발생

필요한 자원은 화면의 크기보다, 호스트에서 동시에 실행하는 에이전트와 개발 작업의 양에 더 크게 좌우됩니다. T3 Code 자체를 사용하는 것과 로컬 LLM을 추가로 실행하는 것은 별도 문제입니다. 로컬 모델을 선택하면 그 모델의 메모리·GPU 조건이 추가됩니다.

7-3. 저장 공간과 네트워크도 요구사항이다

apps/server/src/config.ts의 deriveServerPaths()는 statev2.sqlite, 첨부 파일, 브라우저 산출물, 로그, worktree 경로를 나눕니다. 기본 포트 상수는 3773입니다. 데이터베이스 파일 크기만으로 전체 디스크 사용량을 계산하면 worktree와 산출물 증가를 놓칠 수 있습니다.

비유

휴대폰은 공방의 CCTV와 주문 단말에 가깝습니다. 휴대폰을 새것으로 바꾸어도 공방의 톱이 빨라지지는 않습니다. 반대로 화면이 가벼워도 공방 컴퓨터가 잠들거나 네트워크가 끊기면 일을 제어할 수 없습니다.

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

읽기 두 개, 작은 모형 두 개, 통합 설계 한 개로 깊이를 늘린다

처음 두 과제는 소스를 읽고 표를 만드는 것만으로 끝낼 수 있습니다. 코드를 작성하는 과제는 원본 제품과 분리된 연습용 프로젝트에서 진행합니다. 기존 계정·실제 프로젝트·운영 데이터베이스 없이도 핵심 규칙을 연습하도록 범위를 작게 잡았습니다.

과제 1 · 입문 · 30~45분

구독 요청의 왕복 경로를 한 장에 그리기 규약 읽기

목표: subscribeShell이 선언되고 처리되는 곳을 연결합니다. 저장소 루트에서 다음 읽기 명령으로 시작합니다.

rg -n 'WsOrchestrationV2SubscribeShellRpc' packages/contracts/src/rpc.ts
rg -n 'subscribeOrchestrationV2Shell' apps/server/src/ws.ts
rg -n 'RPC_REQUIRED_SCOPES' apps/server/src/auth/RpcAuthorization.ts
rg -n 'applyShellStreamEvent' packages/client-runtime/src/state/shellReducer.ts
  1. 입력·응답·오류·스트림 여부를 네 칸의 표에 옮깁니다.
  2. 서버 핸들러에서 최초 연결과 재개 연결의 분기를 찾습니다.
  3. 필요한 권한과 클라이언트 상태 갱신 함수까지 화살표로 연결합니다.

완료 기준: “클라이언트 → 계약 → 권한 검사 → 구독 서비스 → 상태 병합”의 다섯 칸에 실제 식별자와 파일 경로가 들어 있어야 합니다. 타입 선언만 보고 구현 동작까지 추정한 부분은 따로 표시합니다.

과제 2 · 입문 · 30분

재연결의 경계값을 손으로 판정하기 조건문 읽기

목표: apps/server/src/orchestration-v2/ThreadStream.ts의 decideThreadResume()을 읽고 아래 표의 판정 이유를 설명합니다. 사건 수와 바이트 수 중 하나만 초과해도 어떤 선택을 하는지 확인합니다.

조건입력예상 선택
정확한 상한128건, 1,048,576바이트, 정상 커서재생
사건 수 초과129건, 100바이트, 정상 커서스냅샷
바이트 초과1건, 1,048,577바이트, 정상 커서스냅샷
커서가 미래afterSequence = 126, highWater = 125스냅샷

추가 질문: 입력이 정확히 상한과 같을 때와 1만큼 넘을 때를 다르게 처리하는 연산자는 무엇인가요? 목록 재개의 1,000 간격과 대화 재개의 128건을 혼동하지 않고 설명해 보세요.

완료 기준: 네 경우의 결과와 이유를 한 문장씩 쓰고, “바이트는 작지만 사건이 많은 상황”이 왜 부담인지 적습니다.

과제 3 · 중급 · 60~90분

최소 목록 상태 갱신기를 만들기 순수 함수

목표: shellReducer.ts를 참고해 아래 학습 모형의 reduce()를 구현합니다. 실제 계약의 축약판이며 제품의 함수와 그대로 호환되지는 않습니다.

type Thread = { id: string; title: string };
type State = { sequence: number; threads: ReadonlyArray<Thread> };
type Item =
  | { kind: "thread.updated"; sequence: number; thread: Thread }
  | { kind: "thread.removed"; sequence: number; threadId: string };

// reduce(state, item): State를 구현한다.
// 제목이 같으면 기존 Thread 객체를 재사용한다.
// 화면 내용이 같아도 수신 sequence는 진행할 수 있다.

확인할 사례: 새 항목 추가, 기존 제목 변경, 같은 항목 재수신, 삭제를 차례로 적용합니다. 메타데이터 보강만 왔을 때 목록을 유지하는 상황도 별도 입력으로 설계해 보세요.

완료 기준: 이전 상태를 직접 수정하지 않고, 같은 값의 재수신 때 스레드 객체 참조를 유지하며, 최종 순서 번호가 진행해야 합니다. 오래된 사건·중복 사건을 처리할 책임을 통신 계층과 reducer 중 어디에 둘지도 기록합니다. 객체를 직접 수정하지 않는 이유는 React 공식 상태 갱신 문서와 비교해 보세요.

과제 4 · 중급 · 90분

명령 영수증과 outbox의 장애 시나리오 만들기 저장 설계

목표: EventSink.ts의 commitCommandEffect()와 EffectWorker.ts를 읽고, 작은 메모리 모형으로 이벤트·영수증·작업 목록을 분리합니다. 실제 공급자를 호출할 필요는 없습니다.

  1. 같은 commandId를 두 번 보내도 이벤트와 실행 의도가 한 번만 추가되게 설계합니다.
  2. 저장은 끝났지만 클라이언트가 응답을 못 받은 상황을 만듭니다.
  3. 외부 작업은 성공했지만 완료 기록은 실패한 상황을 따로 만듭니다.
  4. 각 경우에 재시도가 무엇을 중복시킬 수 있는지 표로 적습니다.

완료 기준: 명령 중복 방지와 외부 실행 중복 방지를 구별해야 합니다. 단순 메모리 예제에는 실제 트랜잭션·프로세스 복구가 없다는 한계도 설명합니다. 작업 처리 결과를 다시 명령으로 넣는 순환을 그리면 더 좋습니다.

과제 5 · 고급 · 반나절

가짜 공급자와 느린 화면의 통합 규칙 설계하기 경계 통합

목표: ProviderAdapterV2Event, LiveStreamBudget.ts, ThreadStream.ts를 참고해 외부 서비스가 없는 연습 프로그램을 만듭니다. 전체 제품 어댑터를 구현하기 전에 메시지 갱신과 실행 종료 두 사건만 흉내 냅니다.

  1. 가짜 공급자가 같은 스레드의 메시지 상태를 여러 번 갱신하고 마지막에 종료 사건을 만듭니다.
  2. 화면은 일부 갱신만 소비하고 연결을 끊도록 구성합니다.
  3. 대기 중 상태 갱신을 합치되, 실행 종료 같은 의미 있는 사건은 보존하는 정책을 작성합니다.
  4. 재접속 시 커서 범위와 사건 수·바이트 예산으로 재생 또는 스냅샷을 고릅니다.
  5. 파일 상태까지 연결하려면 어느 시점에 Git 체크포인트가 필요할지 시퀀스 도식에 추가합니다.

완료 기준: 연결이 끊겨도 최종 화면 상태가 수렴하고, 메모리 보관량의 상한을 설명할 수 있어야 합니다. 테스트는 고정 대기 시간보다 사건 전달·완료 신호를 기준으로 끝나게 설계합니다.

가장 좋은 첫 기여 연습은 기능을 크게 늘리는 것이 아니라, 작은 규칙 하나의 정상·중복·실패 경계를 설명하는 것입니다. 실제 기여를 계획한다면 README와 CONTRIBUTING.md의 현재 수용 범위를 먼저 확인하세요.

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

6주 동안 요청 한 번의 왕복을 점점 더 넓은 경계에서 이해한다

주당 4~6시간을 가정한 학습안입니다. TypeScript 기본 문법이 낯설면 1주차를 두 주로 늘려도 좋습니다. 매주 읽는 코드와 남길 산출물을 연결하면 큰 저장소에서도 진도를 확인할 수 있습니다.

주차배울 내용읽을 코드남길 산출물
1주차모듈, 유니온, 제네릭, 런타임 검증packages/contracts/src/baseSchemas.ts, packages/provider-core/src/server/ProviderAdapter.ts공급자 사건 두 종류의 필드 비교표
2주차React 상태, 불변 갱신, 객체 참조packages/client-runtime/src/state/shellReducer.ts최소 reducer와 네 가지 입력 결과
3주차Effect 서비스, 오류, Scope, 스트림apps/server/src/checkpointing/CheckpointStore.ts, apps/server/src/orchestration-v2/ShellStream.ts서비스 의존성 그림과 자원 정리 시점
4주차이벤트 소싱, 조회 모델, 트랜잭션, outboxapps/server/src/orchestration-v2/EventSink.ts, apps/server/src/orchestration-v2/EffectWorker.ts장애 시점별 데이터 보존 표
5주차WebSocket 구독, 커서, 데이터 예산, 역압apps/server/src/ws.ts, apps/server/src/orchestration-v2/ThreadStream.ts최초 연결과 재연결 시퀀스 도식
6주차Git 객체·인덱스·ref, 권한, 통합 테스트apps/server/src/vcs/GitVcsDriver.ts, apps/server/src/auth/RpcAuthorization.ts가짜 공급자 설계와 체크포인트 연결도

9-1. 다음 단계는 관심 있는 경계 하나를 고른다

매주 스스로 확인할 세 가지
입력, 진실의 위치, 실패 뒤 동작

“무엇을 입력받는가?”, “결과의 근거는 어디에 저장되는가?”, “중간에 끊기면 무엇을 다시 할 수 있는가?”에 답해 보세요. 답에 파일 경로와 함수가 붙으면 개념을 실제 코드와 연결한 것입니다.

이 저장소를 공부한 결과물은 라이브러리 목록이 아니라, 요청의 성공과 실패를 끝까지 설명하는 자기만의 시스템 그림이어야 합니다.

10핵심 키워드 사전

본문을 읽다가 막혔을 때 돌아오는 짧은 설명
용어쉬운 설명
Harness에이전트가 도구를 쓰고 승인을 받으며 작업하도록 관리하는 실행 틀.
Provider연결할 에이전트 실행 체계. 모델 이름 자체보다 넓은 개념이다.
Environment서버 하나와 그 컴퓨터의 파일·인증·실행 상태를 묶은 작업 환경.
Project한 환경 안의 실제 디렉터리를 루트로 삼는 프로젝트 기록.
Thread프로젝트에서 계속 이어지는 대화와 작업 이력의 단위.
Turn / RunTurn은 사용자와 에이전트 사이의 한 작업 주기이며 V2 run으로 관리한다. 공급자 실행 종료와 체크포인트·차이 보기 처리 완료는 별개다.
Adapter공급자 고유의 입력·출력을 앱의 공통 규약으로 번역하는 연결 부품.
Capability특정 서버나 공급자가 실제로 지원한다고 알리는 기능 정보.
RPC다른 프로세스·컴퓨터의 기능을 이름과 입력 데이터로 호출하는 방식.
WebSocket연결을 유지하며 양방향 메시지를 주고받는 통신 방식.
Schema데이터가 어떤 모양이어야 하는지 표현하고 검증하는 규칙.
Command / Event“해 달라”는 요청과 “이 일이 일어났다”는 기록의 차이.
Event sourcing사건 이력을 상태의 근거로 보관하는 설계.
Projection사건에서 만들어 둔 조회용 상태. 화면이 매번 전체 이력을 계산하지 않게 한다.
CQRS변경을 결정하는 경로와 읽기 좋은 결과를 조회하는 경로를 나누는 설계.
Receipt명령이 이미 처리됐는지와 결과 순서 번호를 확인하는 영수증.
Idempotency같은 요청을 반복해도 중복 변경을 만들지 않도록 하는 성질.
Outbox커밋 뒤 해야 할 외부 작업을 의도와 함께 저장하는 작업 목록.
Lease작업자가 일정 조건 아래 해당 작업을 맡았음을 나타내는 임시 소유권.
Snapshot / Delta기준 시점의 상태 묶음과, 그 상태 이후에 바뀐 부분.
Sequence / Cursor사건의 순서 번호와 클라이언트가 어디까지 받았는지 기억하는 위치.
High-water mark재생 시작 때 정해 두는 과거 사건의 상한선.
Shell이 글의 목록 스트림에서는 프로젝트·대화의 최소 요약. 터미널 문맥의 셸과는 다르다.
Coalescing같은 대상의 대기 중 갱신을 합쳐 전달량을 줄이는 것.
Backpressure받는 쪽의 처리 속도에 맞춰 생산·전달·보관량을 제어하는 개념.
Effect / Layer결과·실패·필요 서비스를 표현하는 작업과, 그 서비스를 구성하는 조립 단위.
Scope / Deferred자원을 정리할 수명 범위와, 나중에 확정될 완료 신호를 기다리는 도구.
WALSQLite가 변경을 별도 로그에 먼저 쌓는 방식. Git 체크포인트와 별개다.
Git ref / IndexGit 객체를 가리키는 이름과, 다음 파일 상태를 구성하는 스테이징 자료구조.
Checkpoint작업 전후를 비교·복원하기 위한 파일 상태 기록. 이 구현은 숨은 Git ref를 활용한다.
Worktree같은 Git 저장소의 객체를 공유하면서 별도의 작업 디렉터리를 두는 기능.
Control plane실제 작업 실행과 구분되는 연결·설정·자격증명 관리 계층.
Monorepo여러 앱과 공통 패키지를 한 저장소에서 함께 관리하는 형태.

11참고 링크

개념을 읽은 뒤에는 같은 커밋의 실제 코드로 돌아간다

11-1. 공식 저장소와 사용자 문서

11-2. 핵심 소스로 바로 가기

본문 주제커밋 d7a8d6f3의 근거
버전·워크스페이스루트 package.json · pnpm-workspace.yaml · 잠금 파일
앱별 의존성서버 · 웹 · 데스크톱 · 모바일
RPC 계약·권한rpc.ts · RPC_REQUIRED_SCOPES
공급자 추상화ProviderAdapterV2와 세션 규약 · Pi 어댑터 구현
명령 결정·중복 방지dispatchWithReceipt()와 dispatchWithReceiptEffect()
원자적 저장commitCommandEffect()와 commitThenPublish()
후속 작업 실행layerWithOptions()와 runDaemonWithOptions()
재연결·예산subscribeOrchestrationV2Shell() · decideThreadResume() · 실시간 보관 예산
목록 상태 병합reuseUnchangedThreadShells()와 applyShellStreamEvent()
Git 체크포인트ref 이름 규칙 · captureCheckpoint 구현
SQLiteWAL·동기화 설정 · Node 내장 SQLite와 워커
테스트·CI재연결 테스트 · 완료 대기 구조 · CI 설정
모바일·네이티브모바일 플랫폼 빌드 조건 · 자원 모니터 Rust 의존성
원격 제어 평면relay 책임 설명 · Cloudflare Worker 구성

11-3. 관련 기술 공식 문서

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

항목확인 내용
저장소pingdotgg/t3code
트렌딩GitHub Trending Monthly #16 (2026-10-23 분석 기준)
스타 / 포크29,528 / 7,919 (2026-10-23 GitHub 기준)
주 언어TypeScript
생성일2026-02-08
최신 릴리스v0.1.2 · 2026-10-22
최근 push2026-10-23
최근 커밋d7a8d6f3 · 2026-10-23
fix(server): avoid full shell read on resume (#23987)
코드 규모정의한 집계 범위에서 파일 5,519개 · 1,489,675줄. TypeScript·TSX 합계 1,448,077줄. 공백·주석·테스트·생성 코드 포함
릴리스와 개발 버전서버·웹·데스크톱 manifest는 0.1.2, 모바일 manifest는 0.0.0. 패키지별 version을 제품 전체의 동일 버전으로 해석하지 않음
라이선스MIT · Copyright 2026 T3 Tools Inc.
사용 전에 알아둘 한계
버전·실행 환경·공급자 역량에 따라 결과가 달라진다

이 글의 파일 경로와 설정값은 커밋 d7a8d6f3 기준입니다. 이후의 코드·릴리스·통신 규약은 달라질 수 있습니다. 과거 전체의 커밋 수나 증가 속도는 단일 시점의 코드만으로 알 수 없습니다.

재시도·인증·체크포인트 구조가 있다는 사실은 실제 성능, 모든 장애에서의 데이터 보존, 모든 공급자의 기능 동등성을 보증하지 않습니다. RAM·디스크 권장치는 학습용 추정이며 공식 최소 사양이 아닙니다.

README는 프로젝트 초기 단계의 버그 가능성과 기여 범위 제한을 알립니다. MIT는 T3 Code 소스의 이용 조건이며 외부 공급자의 구독·요금·이용 조건까지 대신하지 않습니다. 실제 작업에서는 사용하는 환경과 공급자의 현재 문서를 함께 확인해야 합니다.