프록시·플러그인
fdbddd0 기준.
billion-context는 오래된 대화 구간을 모델이 작성한 요약으로 접고,
필요하면 저장한 원문을 다시 꺼내는 도구입니다. npm 패키지 이름은 billion-context,
실행 명령은 bili이고, bili-proxy는 같은 진입점을 가리키는 별칭입니다.
수십억 토큰을 한 번에 모델에 넣는 것이 아니라, 작은 창 안에 남길 내용을 계속 고르는 시스템입니다. 제목의 큰 숫자는 장기간 세션에서 다룬 누적 입력량의 맥락으로 읽어야 합니다. 모델 자체의 컨텍스트 창이 커지거나, 요약만으로 모든 세부 정보가 보존되는 것은 아닙니다.
하루 종일 회의하면 책상 위가 회의록으로 가득 찹니다. 끝난 안건은 요약 카드로 바꾸고, 원본 회의록은 서랍에 넣습니다. 새 안건을 다룰 공간은 생기고, 정확한 숫자가 필요할 때는 원본을 꺼냅니다.
책상이 컨텍스트 창, 요약 카드가 압축 블록, 서랍이 원문 저장소입니다. 서랍이 남아 있어야 다시 펼칠 수 있다는 점까지 같은 비유입니다.
| 도구 | 독자가 이해할 동작 | 코드에서 볼 곳 |
|---|---|---|
compress | 메시지 범위와 요약을 받아 압축 블록을 만든다 | kernel/src/compress.ts |
decompress | 요약에 가려진 원문이나 하위 내용을 다시 조회·노출한다 | src/decompress-shared.ts |
search_context | 활성 압축 블록의 제목·요약에서 관련 내용을 찾아 복원할 위치를 좁힌다 | src/decompress-shared.ts |
acp_status | 사용량과 압축 가능한 범위를 확인한다 | src/acp-status.ts |
중요한 경계가 있습니다. 커널의 createCore().decompress()는 블록 조회를 제공하는 작은 함수입니다.
사용자가 보는 전체 복원 동작은 프록시의 원문 저장·조회와 커널의 블록 활성 상태 처리까지 합쳐 완성됩니다.
함수 이름만 보고 제품 기능 전체가 한 함수에 들어 있다고 생각하면 흐름을 놓치기 쉽습니다.
README는 네이티브 플러그인, 클라이언트를 함께 띄우는 런처, API 기본 URL 변경을 안내합니다.
코드에서는 요청의 pluginMode 여부에 따라 도구를 누가 실행하는지가 달라집니다.
연결 방법과 압축 실행 주체를 별개의 축으로 읽으면 구조가 쉬워집니다.
이 저장소는 대화 데이터를 바꾸는 미들웨어의 사례입니다. 입력을 줄이면서도 메시지의 순서, 도구 호출의 짝, 원문 복원, API 호환성과 장애 복구를 함께 지키는 방법을 배울 수 있습니다.
코딩 에이전트는 파일 내용, 검색 결과, 명령 출력, 수정 내역을 계속 대화에 붙입니다. 요청마다 많은 이력을 다시 전송하면 입력량이 커지고, 창이 차면 정리 작업이 필요합니다. 이 프로젝트는 조금씩 접기·필요하면 펼치기·여러 클라이언트에 연결하기를 한 제품에 묶었습니다.
2026-10-23 GitHub 기준 스타 2,452개, 포크 210개이며 GitHub Trending TypeScript Weekly #16에 선정됐습니다. 이 지표만으로 사용자 유입 원인을 확정할 수는 없지만, 아래의 설계가 기술적으로 관심을 끌 만한 지점입니다.
다음 표는 처리 방식의 비교입니다. 특정 제품의 품질 순위나 비용 벤치마크를 뜻하지 않습니다.
클라이언트별 설치 차이는 이 저장소의 CLIENTS.md와 실제 어댑터를 함께 읽어야 합니다.
| 접근 / 제품군 | 문제를 푸는 위치 | billion-context의 학습상 차별점 |
|---|---|---|
| 큰 컨텍스트 모델 | 모델이 받는 입력 용량을 늘린다 | 주어진 창 안에서 무엇을 남길지 관리한다. 모델 교체와는 다른 선택이다 |
| 호스트의 자동 요약 | 각 에이전트 내부에서 이력을 정리한다 | 범위·블록·원문 조회를 공통 엔진으로 다룬다. OpenCode와 병용 시 중복 압축 주의가 README에 명시돼 있다 |
| opencode-acp | README상 OpenCode 1.x용 독립 확장 | 본 저장소는 OpenCode 외에도 여러 호스트의 연결·실행·업데이트 경로를 포함한다 |
| billion-context-pi | README상 pi용 독립 확장 | 본 저장소에는 pi 네이티브 진입점과 공용 프록시가 함께 있다. 동시 활성화는 중복 압축 위험이 있다 |
| 일반적인 RAG | 검색으로 필요한 문서 조각을 고른다 | 대화의 시간 순서와 도구 실행 이력을 접고 복원한다. 내부 검색 기능은 이 과정을 보조한다 |
이 프로젝트의 강점은 요약 하나가 아니라, 요약을 오래 유지하는 데 필요한 상태와 복구 경로를 함께 구현했다는 점입니다.
| 프로젝트 측 설명 | 소스에서 확인되는 장치 | 읽는 기준 |
|---|---|---|
| 100K 창으로 장기 세션 | 모델별 창 설정, 보호 구간, 압축 권고, 사전 예산 검사 | 모든 작업이 100K에서 성공한다는 보장은 아니다 |
| 토큰 5배 절감 | 요약 치환, 도구 결과 저장, 사용량·캐시 집계 | README의 홍보 설명이다. 작업별 동일 절감률을 보장하지 않는다 |
| 캐시 적중률 95~97% | 안정적인 앞부분, 캐시 사용량 원장, 바이트 차이 검증 코드 | README의 정상 세션 설명이다. 제공자·TTL·모델 전환에 따라 달라진다 |
| 압축을 되돌릴 수 있음 | 블록 원문 보관, 중첩 블록 관계, 복원 도구 | 요약 자체의 무손실성을 뜻하지 않는다. 저장된 원문이 복원을 뒷받침한다 |
여행 가방을 작게 싸는 기술과, 모든 여행에서 정확히 같은 무게를 줄인다는 주장은 다릅니다. 무엇을 챙겼는지, 무엇을 다시 꺼내야 하는지에 따라 절감량과 추가 작업이 달라집니다.
코드를 읽을 때도 같은 태도가 유용합니다. 메모리 절감 장치를 발견했다면 적용 조건을 찾고, 복구 함수를 발견했다면 복구할 데이터가 실제로 저장되는 경로까지 따라가야 합니다.
루트 package.json은 ESM을 뜻하는 "type": "module"과
Node.js >=20을 선언합니다. tsconfig.json은
strict: true, target: ES2022,
module: NodeNext, moduleResolution: NodeNext입니다.
| 역할 | 의존성 선언 | 실제로 읽을 코드 |
|---|---|---|
| HTTP 서버 | Node.js 내장 node:http | src/server.ts의 startServer(), http.createServer(dispatch) |
| 압축 커널 | acp-kernel: file:./kernel자체 버전 0.0.160 | kernel/src/compress.ts의 createCore() |
| 스키마 호환 | zod: 4.1.8 | OpenCode 네이티브 도구 등록 경로. 모든 진입점의 공통 프레임워크는 아니다 |
| 네트워크·스트림 | undici: ^7.29.0ws: 8.22.0 | 상류 요청, WebSocket 중계와 Responses 연결 |
| 인증서 | node-forge: ^1.4.0 | src/ca.ts, src/mitm.ts의 인증서 기반 중계 |
| 영속 저장 | SQLite 엔진 선택 + mysql2: ^3.15.3 | src/storage/driver.ts, src/storage/backend.ts, src/storage/mysql.ts |
| 선택적 이미지 처리 | jimp: 1.6.1 — 선택 의존성 | src/image-rescale.ts에서 필요할 때 로드 |
| pi 하위 에이전트 | billion-context-pi-subagents: file:./pi-subagents자체 버전 0.2.2 | pi-subagents/src/와 src/agent/pi-subagents.ts |
여기서 ^는 버전 범위입니다. package-lock.json의 루트 설치 해석값은
TypeScript 5.9.3, tsup 8.5.1, tsx 4.23.9,
esbuild 0.27.7입니다. 선언된 최소 범위와 고정된 설치 결과를 구분해서 읽어야 합니다.
import·export로 파일을 연결하는 모듈 방식입니다.
번들은 여러 모듈을 배포 파일로 묶는 작업입니다. 빌드 때 내부로 묶인 라이브러리와,
실행할 때 별도 패키지로 찾는 의존성은 설치·장애 조건이 다릅니다.src/web/page.ts의 renderPage()가 HTML을 조립하고,
src/web/client.ts와 src/web/styles.ts가 브라우저 동작과 스타일을 제공합니다.
설정 편집, 세션 조회, 로그 탐색을 위해 별도의 React·Vue 애플리케이션을 빌드하는 구조는 아닙니다.
| 화면 계층 | 구현 방식 | 배울 점 |
|---|---|---|
| 내장 관리 화면 | renderUI() → renderPage() | 서버 버전·실행 상태를 화면과 연결하는 방법 |
| 설정 편집기 | parseConfigDraft(), 주석·후행 쉼표 처리 | JSONC를 읽는 화면과 서버의 해석을 일치시키기 |
| 세션 탐색 | src/web/sessions-data.ts | 목록은 작게 보내고 블록 내용은 필요할 때 읽는 조회 설계 |
| dsh 화면 연동 | src/agent/dsh-native-client.ts, React는 호스트 외부 의존성 | 호스트 UI에 들어가는 작은 어댑터와 본체 UI를 구분하기 |
| 공식 웹사이트 | website/의 HTML·CSS·JavaScript | 정적 문서 사이트와 실행 중 관리 화면은 별개의 산출물이다 |
React의 별도 버전 범위는 루트 의존성에 없습니다. tsup.config.ts의 브라우저용 설정에서
react를 외부로 남겨 dsh 호스트의 모듈 로더가 제공하게 합니다.
따라서 이 저장소 전체를 React 앱이라고 부르면 실제 구조와 맞지 않습니다.
| 영역 | 설정과 버전 | 구체적인 의미 |
|---|---|---|
| TypeScript 도구 | 루트 선언 typescript: ^5.5.0, tsup: ^8.0.0, tsx: ^4.0.0 | 타입 검사·번들·TypeScript 테스트 실행을 분담한다 |
| Node.js용 번들 전략 | target: node20, splitting: false, sourcemap: false | 여러 진입점을 독립 파일로 만들고 소스맵 배포를 끈다 |
| 내부 패키지 포함 | noExternal에 커널·pi 구성요소 등 명시 | 내부 엔진을 빌드 결과에 포함한다. 커널의 별도 npm 조회에 기대지 않는다 |
| 테스트 | node:test, node:assert/strict | 루트 npm test는 전용 잠금·실행 스크립트를 거친다 |
| CI | .github/workflows/ci.ymlUbuntu·Windows, Node.js 22·24 | core / full / skip 범위를 결정하고 테스트를 분할한다 |
| 컨테이너 | FROM node:24-slim, BILI_VERSION=latest | 소스 컴파일 대신 공개 npm 패키지를 설치한다. 버전 인자로 고정할 수 있다 |
| 배포 자동화 | .github/workflows/release.yml, .github/workflows/pages.yml | 릴리스는 검사 후 npm 배포, 웹사이트는 빌드 없이 정적 폴더 게시 |
AGENTS.md 일부 설명에는 의존성이 없거나 zod만 외부라는 표현이 남아 있습니다.
현재 package.json에는 mysql2와 zod가 실행 의존성,
jimp가 선택 의존성으로 선언돼 있습니다. 일부 기능이 지연 로드된다는 사실과
패키지 전체에 실행 의존성이 없다는 주장은 구분해야 합니다.
HTTP 서버 진입점은 src/server.ts의 startServer()입니다.
여기서 createEngineContext()로 엔진과 상태를 준비하고,
http.createServer(dispatch)가 요청을 받습니다.
실제 라우팅과 전달은 src/server/handle.ts와 src/server/relay.ts로 나뉩니다.
이 그림의 압축 루프는 프록시 모드의 대표 경로입니다. 플러그인 모드에서는 호스트가 도구를 실행하므로
같은 루프가 모든 요청에서 같은 일을 한다고 해석하면 안 됩니다.
prepareOpenai()의 injectTools = shouldInject && !pluginMode가 이 경계를 드러냅니다.
kernel/src/types.ts의 CoreMessage는
id, role, contentType, text,
toolName, toolCallId 같은 공통 필드를 둡니다.
Anthropic·OpenAI·Responses·Google 형식 변환은 kernel/src/wire/가 담당합니다.
엔진은 API마다 다른 JSON 필드를 직접 알 필요가 줄어듭니다.
토큰 계산도 createCore(ports)의 Ports.countTokens로 주입할 수 있습니다.
입력 형식과 환경 의존 동작을 바깥으로 밀어낸 설계입니다.
// kernel/src/compress.ts — 실제 공개 인터페이스 일부
export interface Ports {
countTokens?: (text: string) => number;
}
// kernel/src/types.ts — 학습을 위해 핵심 필드만 발췌
export interface CoreMessage {
id: string;
role: MessageRole;
contentType: MessageContentType;
text?: string;
toolName?: string;
toolCallId?: string;
}
출력 스트림에도 같은 원리가 적용됩니다. src/loop/core.ts의
CompressLoopAdapter는 buildRequest(), parseStream(),
emitText(), emitToolCall() 등을 요구합니다.
공통 루프는 ParsedStreamEvent를 보고 판단하고, 실제 API 바이트 형식은 어댑터가 만듭니다.
kind: "text"와 kind: "tool_call"처럼
구분 필드가 있는 여러 타입을 하나로 묶는 방식입니다. kind를 확인하면
TypeScript가 그 경우에 존재하는 필드를 좁혀 줍니다. 스트림 이벤트의 잘못된 조합을 줄이는 데 유용합니다.배울 점은 “모든 API를 완전히 똑같게 만든다”가 아닙니다.
서명·도구 식별자처럼 공통 필드만으로 충분하지 않은 정보는 별도로 보존해야 합니다.
실제 이벤트 타입에 signature, redacted_thinking,
standalone_signature가 있는 이유입니다.
processTurn()은 buildNodes()가 만든 노드 목록을
runPipeline()에 넘깁니다. 각 노드는 메시지·상태·부수 결과를 담은
NodeIO를 받아 다음 단계로 전달합니다.
// kernel/src/pipeline.ts — 실제 순회 로직
let io = initial;
for (const node of nodes) {
if (node.enabled && !node.enabled(io, ctx)) continue;
io = node.run(io, ctx);
}
return io;
여기서 깊이 읽을 함수가 kernel/src/unit-projection.ts의 projectUnit()입니다.
범위 경계, 도구·추론 쌍, 보호 도구, 미디어, 최근 메시지와 턴 무결성을 함께 검사합니다.
applyCompression()의 실행 경로와 추천 경로가 같은 판단 함수를 사용합니다.
이전처럼 추천은 전체 압축 후보를 한꺼번에 보고, 실행은 제출된 범위 하나만 보면 모순이 생길 수 있습니다.
“압축 가능”이라고 안내한 구간이 실제 실행에서는 도구 호출의 짝을 끊어 매번 거부되는 식입니다.
applyView는 두 판단이 같은 종류의 메시지 배열을 보도록 전달하는 장치입니다.
추천과 실행의 규칙을 공유해야, 가능하다고 보여 준 작업이 실제로도 같은 조건에서 가능해집니다.
예매 화면은 좌석이 비었다고 표시하는데 결제 서버는 늘 매진이라고 답한다면, 두 화면을 예쁘게 고쳐도 문제가 해결되지 않습니다. 두 판단이 같은 좌석 상태와 규칙을 보게 해야 합니다.
CompressionBlock에는 summary 외에도
directMessageIds, effectiveMessageIds, directBlockIds,
tier, generation, active가 있습니다.
직접 포함한 메시지, 중첩한 하위 블록, 결과적으로 덮는 원문 범위를 구분합니다.
위 번호와 구간은 개념 예시입니다. 실제 범위는 보호 규칙을 통과해야 합니다.
kernel/src/prune.ts의 prune()가 덮인 메시지를 뷰에서 빼고 요약을 배치합니다.
활성 블록의 요약은 acp_summary_ 접두의 뷰 전용 메시지로 나타날 수 있습니다.
플러그인 모드에서는 호스트 이력에 남은 압축 도구 호출이 요약을 운반할 수 있습니다.
프록시 모드에서는 acp_summary 계열 메시지가 운반할 수 있습니다.
prune()는 해당 블록의 원래 압축 호출이 이미 보이면 요약을 중복 렌더링하지 않도록 검사합니다.
핵심은 저장 상태와 모델에게 보여 줄 배열을 같은 것으로 취급하지 않는 것입니다.
또한 tier 1·2·3과 generation의 young·old는 서로 다른 필드입니다.
계층의 높이와 블록이 살아남은 세대 상태를 하나의 숫자로 뭉개지 않았습니다.
kernel/src/config.ts | 기본값 | 읽을 때 주의할 점 |
|---|---|---|
growthFloor / growthCap | 각각 50,000 | 성장 간격의 기준을 고정한다. 매번 정확히 50,000에서 무조건 압축하는 뜻은 아니다 |
preserveRecentMessages | 3 | 최근 보호의 한 축이다. 사용자 메시지·토큰·도구 쌍 규칙도 함께 작동한다 |
preserveRecentTokens | 5,000 | 메시지 개수만으로 보호 범위를 결정하지 않는다 |
minCompressRange | 5,000 | 토큰 수가 아니라 원문 문자열의 UTF-16 코드 단위 길이 기준이다 |
tier2Trigger / tier3Trigger | 1,000 / 2,000 | 하위 계층 블록 수 기준이다. 토큰 수로 읽으면 안 된다 |
truncate.threshold | 0.95 | 긴급 절단의 안전장치다. 정상 요약 흐름과 구별한다 |
absorb / crush / ccr | 커널에서는 기본 비활성 | 프록시 설정·호스트 경로에서 바뀔 수 있으므로 최종 설정을 확인한다 |
캐시도 이 뷰 설계와 연결됩니다. 앞부분이 조금만 바뀌어도 재사용 가능한 접두부가 줄어들 수 있어,
요약 삽입 위치와 참조 태그를 안정적으로 유지하려는 코드가 많습니다.
src/server/snapshot-render.ts는 서버의 정식 이력에서 앞부분을 렌더링하는 별도 경로지만,
src/rollout.ts의 snapshotRender 기본 배포 비율은 0입니다.
src/session.ts의 LazyBlockContents는 저장을 마친 원문 내용을
메모리에서 내렸다가, get()·반복·size 조회 시 다시 읽습니다.
다시 읽기에 실패하면 빈 Map을 정상 데이터로 반환하지 않고 예외를 냅니다.
이 선택은 데이터 보존과 직결됩니다. 원문이 사라진 것처럼 보이는 빈 상태를 다음 저장에서 기록하면
디스크의 정상 원문까지 지울 수 있기 때문입니다.
tests/memory-blockcontent-lazy.test.ts는 이 동작을 읽어 볼 회귀 테스트입니다.
파일 저장은 kernel/src/persist/store.ts의 StateStore.writeInner()에서
임시 파일 쓰기 → 파일 동기화 → 세대 확인 → 이름 변경 순서로 공개됩니다.
commitSeq가 바뀌었다면 오래된 쓰기를 버려 더 최근 상태를 덮지 않게 합니다.
// src/persist.ts — 통합 인덱스 일반 조회 경로의 복구 조건을 축약
const ahead = envRequests > indexedRequests ||
(envRequests === indexedRequests &&
envelopeNextBlockId > indexNextBlockId);
// 앞선 JSON 저장본이면 통합 저장소 반영을 시도하고 그 상태를 사용
// 같거나 뒤처진 저장본이면 기존 인덱스 상태를 유지
재시작 뒤 처음 읽는 경로에서는 통합 인덱스와 JSON 저장본이 서로 어긋날 수 있습니다.
SessionStore.loadSync()의 통합 인덱스 일반 조회 경로는 요청 수가 더 큰지, 같다면 nextBlockId가 더 큰지 비교합니다.
별도의 불일치 복구 경로에서는 활성 블록 수와 참조 수를 비교하기도 합니다.
파일 수정 시각 대신 데이터의 진행 상태를 복구 기준으로 사용한 사례입니다.
tests/storage-orphan-recovery.test.ts에는 인덱스 요청 수 390,
JSON 저장본 요청 수 876인 상황을 만들어 후자를 복원하는 사례가 있습니다.
복원 뒤 JSON 파일을 지우고 재시작해도 인덱스가 복구 상태를 유지하는지까지 검사합니다.
지연 로딩 실패를 빈 데이터로 바꾸지 않는 규칙, 오래된 쓰기를 거부하는 세대 번호, 두 저장본의 진행 상태 비교는 모두 “무엇을 진실로 취급할 것인가”라는 같은 질문에 대한 답입니다.
트리는 학습에 필요한 경로를 골라 축약했습니다. 특히 kernel/src/persist/는
파일을 안전하게 쓰는 범용 기구이고, src/persist.ts는 제품의 세션 저장 정책입니다.
이름이 비슷해도 책임의 크기가 다릅니다.
| 읽는 목적 | 추천 순서 | 이 순서가 유용한 이유 |
|---|---|---|
| 전체 그림 | package.json → src/index.ts → src/server.ts | 무엇이 실행되는지부터 정한다 |
| 압축 원리 | kernel/src/types.ts → kernel/src/config.ts → kernel/src/compress.ts | 자료형과 기본값을 알고 동작을 읽는다 |
| 원문 복원 | src/session.ts → src/decompress-shared.ts → src/persist.ts | 메모리 상태·도구 처리·디스크를 연결한다 |
| 호환성 문제 | kernel/src/wire/ → src/loop/ → 해당 회귀 테스트 | 입력 변환과 출력 재구성을 모두 본다 |
커밋 fdbddd0의 Git 추적 텍스트 파일을 확장자별로 센 수치입니다.
빈 줄과 주석, 끝에 개행이 없는 마지막 줄도 포함한 물리적 줄 수입니다. 테스트 지원 코드·선언·설정도 포함됩니다.
TypeScript가 많다는 사실과 제품 구현이 그만큼 길다는 주장은 구분해야 합니다.
| 언어 / 확장자 | 파일 수 | 줄 수 |
|---|---|---|
TypeScript .ts | 1,395 | 466,088 |
JavaScript .js + .mjs | 30 | 6,861 |
Python .py | 2 | 1,554 |
| HTML | 5 | 711 |
| CSS | 1 | 281 |
Shell .sh | 1 | 33 |
TypeScript 중 테스트 경로·테스트 파일은 303,709줄, 나머지 구현·설정·선언은 162,379줄입니다.
src/ 구현은 128,146줄, kernel/src/는 23,413줄,
pi-subagents/src/는 4,648줄입니다. 이 세 하위 경로 수치는 전체 집계와 겹칩니다.
처음부터 46만 줄을 읽으려 하지 말고, 작은 자료형 하나와 그 자료형을 지키는 테스트 하나를 짝지어 읽으세요.
reference/architecture.md의 테스트 개수 같은 요약은 현재 파일 집계보다 오래될 수 있습니다.
현재 tests/를 비롯한 저장소 전체의 *.test.ts 파일은 합계 1,013개입니다.
CompressionTier = 1 | 2 | 3, BlockGeneration = "young" | "old"처럼
가능한 값을 좁혀 두면 임의의 숫자·문자열을 넘기는 실수가 줄어듭니다.
UnitProjectionFatal의 kind: "zone" | "split"은
“실패했다”보다 구체적인 사유를 전달합니다.
실습 아이디어: CoreMessage에서 도구 호출과 결과를 구분하는 필드를 표시하고,
ParsedStreamEvent의 각 kind에서 읽을 수 있는 필드를 표로 정리합니다.
타입이 금지하는 값과 런타임 검사로만 막을 수 있는 값을 나눠 보세요.
kernel/src/tokenize.ts의 기본 계산은 정확한 모델별 토크나이저가 아닙니다.
지정한 한·중·일 문자 범위는 글자 수를 세고, 나머지는 UTF-16 코드 단위 4개를 한 토큰으로 추정해 올림합니다.
이 선택은 한국어를 단순히 전체 길이 ÷ 4로 계산하는 과소 추정을 줄입니다.
// kernel/src/tokenize.ts — defaultCountTokens()의 실제 핵심
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
const cjkCount = cjk?.length ?? 0;
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
// 손으로 계산해 보기
// "가나다abcd" → 3 + ceil(4 / 4) → 4
// 이 값은 해당 함수의 추정값이며 모델 청구 토큰 수가 아니다.
실습 아이디어: 영어 코드, 한국어 설명, 이모지가 있는 세 문장을 골라 손으로 계산합니다.
JavaScript의 text.length는 UTF-16 코드 단위 길이라는 점도 확인합니다.
이후 src/server.ts의 effectiveTokenCount()에서 사용량과 추정값을 구분하는 흐름을 읽습니다.
runCompressLoop()는 AsyncGenerator<Buffer>를 반환합니다.
한 번에 완성된 응답만 만드는 대신, 읽은 이벤트에 맞춰 결과 바이트를 조금씩 내보냅니다.
MAX_LOOP_ROUNDS = 10은 내부 루프를 제한하는 상수입니다.
본문, 추론, 도구 호출, 사용량, 종료 이벤트는 의미가 다릅니다.
특히 서명된 추론 데이터는 “보기 좋게 정리”하려고 바꾸면 다음 재전송에서 실패할 수 있습니다.
tests/issue1960-thinking-verbatim.test.ts는 클라이언트 출력과 내부 재요청 양쪽의 보존을 검사합니다.
실습 아이디어: 텍스트 → 도구 호출 → 도구 결과 → 재요청 → 종료라는 타임라인을 그립니다. 이미 독자에게 전달한 텍스트가 있는 시점에 재시도하면 어떤 중복 문제가 생기는지도 적어 봅니다.
kernel/src/search/registry.ts는 substring·BM25·fuzzy·hybrid를 기본 등록합니다.
이 모듈의 기본 알고리즘은 hybrid이고,
kernel/src/search/algorithms/hybrid.ts는 각 점수의 최댓값으로 정규화한 뒤 BM25 0.7, fuzzy 0.3으로 합칩니다.
프록시 도구 search_context는 이 모듈 대신 createCore().search()로 활성 블록의 제목·요약을 검색하며, 기본 결과 수는 5개입니다.
여기서 hybrid는 기본적으로 BM25와 fuzzy의 혼합입니다. “키워드 + 벡터 검색이 기본 활성”이라는 뜻은 아닙니다. 비동기 semantic 경로는 별도 알고리즘을 등록해 사용하는 확장 지점입니다.
실습 아이디어: 문서 세 개에 가상의 두 점수를 넣어 정규화 전후 순위를 비교합니다. 역할 가중치는 확률이 아니므로 최종 점수가 반드시 1 이하일 필요가 없다는 점도 확인합니다.
StorageBackend는 SQL 문법을 통일하기보다 ingestSession(),
getBlocks(), archiveSession() 같은 작업을 계약으로 둡니다.
SQLite와 MySQL의 트랜잭션 방식이 달라도 같은 제품 동작을 구현할 수 있게 한 경계입니다.
검증 계층도 구분합니다. 일반 회귀 테스트는 특정 장애를 재현하고,
proof/는 명세의 가정 아래 성질을 검사하며,
verif/는 지원하는 TypeScript 부분을 공식으로 옮겨 반례를 찾습니다.
세 계층을 합쳐 “전체 프로그램이 증명됐다”고 말할 수는 없습니다.
좋은 테스트는 한 번의 정답뿐 아니라, 입력이 늘거나 다시 읽혀도 유지돼야 할 규칙을 설명합니다.
실습으로 “새 메시지 추가가 이미 완료된 앞부분의 바이트를 바꾸지 않는다”를 문장으로 적고,
tests/cache-proof.test.ts에서 그 규칙을 어떤 상황에 적용하는지 찾아보세요.
| 항목 | 소스에서 확인되는 조건 | 학습·사용 시 해석 |
|---|---|---|
| Node.js | engines.node: >=20, 번들 타깃 node20 | 패키지가 선언한 최소 런타임 계약이다 |
| 검사 환경 | 주 CI는 Node.js 22·24의 Ubuntu·Windows 조합 | 최소 선언과 실제 CI 조합을 구분한다 |
| 컨테이너 | node:24-slim, 포트 8787, 상태별 볼륨 네 개 | Node.js 런타임과 데이터 보존을 함께 준비한다 |
| CPU | 특정 코어 수의 공식 최소값 없음 | JSON 처리·문자열 변환·검색·압축·저장이 CPU를 쓴다 |
| GPU | 기본 프록시 경로는 모델 가중치를 로컬에 올리지 않음 | 상류 모델 API를 쓸 때 프록시 자체에 추론용 GPU는 필요하지 않다 |
| 메모리 | 세션 추정 상주량 기본 목표 512 MiB | 프로세스 전체 RAM 상한도, 시스템 최소 RAM도 아니다 |
| 디스크 | 요약·원문·인덱스·로그가 누적됨 | 컨텍스트 감소가 저장 용량 감소와 같지는 않다 |
| 네트워크 | 모델 API·선택적 레지스트리 조회·업데이트 경로 | 상류 주소·인증·연결 지연이 실행 조건에 포함된다 |
src/knobs.ts의 기본값은 sessions.max = 256,
sessions.warmMax = 0, sessions.warmMaxBytes = 64 MiB,
sessions.residentMaxBytes = 512 MiB입니다.
warmMax가 0이므로 시작할 때 세션을 미리 읽기보다 첫 접근에서 불러오는 흐름이 기본입니다.
512 MiB는 세션 자료의 추정 목표값이며, Node.js 전체 메모리를 그 이하로 고정하는 제한이 아닙니다. 활성 요청의 상태는 유지되고, 파싱 중 객체·응답 버퍼·실행 환경의 메모리도 별도로 듭니다. 소스에는 공식적인 “RAM 몇 GB 이상” 성능 보증이 없습니다.
src/storage/driver.ts와 src/storage/engine-bootstrap.ts는
내장 node:sqlite → 호스트가 제공한 better-sqlite3 →
캐시한 사전 빌드 엔진 순으로 경로를 마련합니다.
소스는 내장 모듈의 제공 시작점을 Node.js 22.5로 설명하고, 엔진을 얻지 못하면 legacy JSON 저장으로 내려갑니다.
실제 경로는 런타임·플랫폼·다운로드 허용 설정에 따라 달라집니다.
storage.unified의 기본 해석은 full이고,
storage.jsonWriter는 기본 켜짐입니다.
MySQL은 선택적 별도 경로이며, 기본 사용에 별도 MySQL 서버가 필수인 것은 아닙니다.
src/paths.ts는 설정·데이터·캐시·실행 상태를 XDG 경로로 나눕니다.
예를 들어 원문과 세션은 ~/.local/share/billion-context/,
로그는 ~/.local/state/billion-context/ 계열에 놓입니다.
컨테이너의 네 볼륨도 이 구분을 반영합니다.
sessions.gc는 기본 비활성입니다. 따라서 긴 대화를 많이 저장한다면
보존 기간·백업·정리 정책을 별도로 결정해야 합니다.
Dockerfile은 컨테이너 안의 자동 업데이트를 ACP_AUTO_UPDATE=0으로 끄지만,
이미지 빌드의 기본 패키지 버전은 latest라는 점도 재현성과 연결됩니다.
할 일: package.json의 bin·main·exports를
각각 적고, tsup.config.ts에서 대응하는 소스 진입점을 찾습니다.
완료 기준: bili 실행과 패키지를 플러그인으로 불러오는 일이
왜 다른 진입점으로 이어지는지 화살표 세 개로 설명합니다.
힌트: exports["."]는 dsh 네이티브 진입점,
exports["./server"]는 OpenCode 네이티브 진입점입니다.
main만 보고 모든 사용 방식을 추정하지 마세요.
할 일: 사용자 요청·도구 호출·도구 결과·답변·새 요청이 있는 대화 여섯 개를 만듭니다. 각 항목에 원문 ID와 표시 참조를 붙이고, 완료된 구간을 b1 요약으로 표시합니다.
완료 기준: 원문 목록, 저장한 블록, 모델에 보낼 뷰를 각각 그립니다. 도구 호출만 요약하고 결과를 남겼을 때 왜 짝이 깨지는지 설명합니다.
힌트: 원문 ID·mNNNNN·bN은 서로 다른 역할입니다.
참조 숫자가 연속이라는 가정도 하지 않습니다.
할 일: kernel/tests/issue3236-recommend-apply-consistency.test.ts에서
늦게 도착하는 도구 결과가 있는 대화 모양을 읽고, projectUnit()의 검사 순서와 연결합니다.
완료 기준: 같은 구간을 전체 후보 집합으로 검사했을 때와 개별 범위로 검사했을 때 판단이 달라지는 예를 하나 그립니다. 추천·실행에 같은 입력이 필요한 이유를 세 문장으로 적습니다.
확장: 별도 학습용 예제의 함수 계약으로
checkRange(messages, range)를 설계하고, 화면과 실행이 그 결과를 함께 쓰게 해 봅니다.
할 일: 요약 세 개에 BM25 점수와 fuzzy 점수를 임의로 부여하고, 최댓값 정규화 → 0.7·0.3 혼합 → 역할 가중치 순으로 계산합니다.
완료 기준: 오타에 강한 점수의 비중을 올렸을 때 어떤 문서가 올라오는지 표로 보여 줍니다.
이 검색 모듈의 기본 minScore = 0.01, limit = 10과 순위 계산의 역할도 구분합니다.
힌트: kernel/src/search/index.ts의 runSearch()와
kernel/src/search/algorithms/hybrid.ts를 연결하면 됩니다.
할 일: tests/storage-orphan-recovery.test.ts를 읽고,
인덱스와 JSON 저장본의 요청 수·다음 블록 번호를 바꾼 가상 사례 여섯 개를 만듭니다.
완료 기준: 어느 저장본을 선택하는지, 선택 뒤 인덱스에 반영되는지, JSON 저장본 없이 재시작해도 복구가 유지되는지를 각각 표시합니다.
확장: 요청 수가 같고 블록 번호만 다른 경우, 복구 중 저장 오류, 지연 원문 로딩 실패까지 고려해 “실패를 빈 데이터로 바꾸면 안 되는 이유”를 설명합니다.
다음 명령은 저장소 루트 기준입니다. 프로그램을 시작하지 않고 정의와 참조 위치를 확인합니다.
rg -n 'createCore|processTurn|applyCompression' kernel/src/compress.ts
rg -n 'projectUnit|applyView' kernel/src
rg -n 'LazyBlockContents|ensure\(' src/session.ts
rg -n 'ahead|envRequests|indexedRequests' src/persist.ts
wc -l kernel/src/compress.ts src/session.ts src/persist.ts
| 주차 | 배울 것 | 읽을 경로 | 작은 결과물 |
|---|---|---|---|
| 1주차 | ESM, strict 타입, 유니온, 패키지 진입점 | package.json, tsconfig.json, kernel/src/types.ts | 실행·플러그인 진입점 지도와 메시지 타입 표 |
| 2주차 | 공통 모델, 어댑터, 참조 식별자 | kernel/src/wire/openai.ts, kernel/src/wire/anthropic.ts, kernel/src/refs.ts | 같은 대화를 두 API 형식과 공통 형식으로 그린 비교표 |
| 3주차 | 파이프라인, 보호 구간, 계층 요약 | kernel/src/compress.ts, kernel/src/prune.ts, kernel/src/unit-projection.ts | 전후 입력 뷰와 압축 불가 구간의 이유 목록 |
| 4주차 | 비동기 제너레이터, 스트림 종료·재시도 | src/loop/core.ts, src/loop/adapter-openai.ts, src/loop/adapter-anthropic.ts | 도구 호출과 내부 재요청이 있는 이벤트 타임라인 |
| 5주차 | 트랜잭션, 원자적 공개, 지연 로딩 | src/session.ts, src/persist.ts, src/storage/backend.ts | 복구 판정표와 데이터가 사라질 수 있는 지점 분석 |
| 6주차 | 캐시 접두부, 불변 조건, 검증의 한계 | tests/cache-proof.test.ts, proof/README.md, verif/README.md | 유지해야 할 성질 다섯 개와 각 성질의 검증 범위 |
JavaScript가 익숙하지 않다면 1주차를 늘려 객체·배열·Map·Set·Promise부터 익히세요. 압축 알고리즘을 먼저 깊게 파기보다 메시지 배열이 어떻게 변하는지 그릴 수 있어야 이후 코드가 읽힙니다. 각 주차는 파일 전체 완독보다 결과물 하나를 완성하는 것을 목표로 삼습니다.
src/server/relay.ts와 src/storage/driver.ts가 다음 읽을 거리입니다.여섯 주 뒤의 목표는 코드를 전부 외우는 것이 아니라, 메시지 하나가 들어와 저장되고 다시 보이는 과정을 설명하는 것입니다.
이 데이터의 원본은 어디에 있는가? 지금 보고 있는 것은 원본인가, 파생된 뷰인가? 두 저장본이 다르면 어떤 기준으로 하나를 고르는가? 이 질문을 다른 데이터 처리 프로젝트에도 적용해 보세요.
| 용어 | 쉬운 설명 |
|---|---|
| 컨텍스트 창 | 한 번의 모델 요청에서 다룰 수 있는 작업 공간. 장기 세션의 누적 사용량과 다르다 |
| 토큰 예산 | 대화·지시·도구·출력 등에 나눠 쓸 용량. 문자열 길이만으로 정확히 계산되지는 않는다 |
| 프록시 | 클라이언트와 서버 사이에서 요청·응답을 중계하는 프로그램. 이 저장소에서는 대화 변환도 수행한다 |
| 호스트 | 플러그인을 실행하는 에이전트 프로그램. 호스트마다 도구·세션·네트워크 연결 방식이 다르다 |
| ACP | 이 코드에서 압축 도구·참조 태그·상태에 붙는 이름. 다른 프로젝트의 같은 약어와 자동으로 동일시하지 않는다 |
| CoreMessage | 여러 API 메시지를 압축 엔진이 다룰 수 있게 정리한 공통 자료형 |
| 어댑터 | 외부 API의 구체적인 형식과 내부 공통 형식 사이를 번역하는 부품 |
| 파이프라인 | 앞 단계의 결과를 다음 단계로 넘기는 처리 구조. 순서가 결과에 영향을 준다 |
| 원문 ID | 메시지의 실제 정체성을 추적하는 키. 화면에 보여 주는 참조 번호와 구분한다 |
| ref · mNNNNN | 모델과 도구가 메시지를 지정할 때 쓰는 세션 내 참조. 배열 인덱스가 아니며 중간 번호가 비어 있을 수 있다 |
| block · bN | 요약, 덮는 메시지 범위, 하위 블록 관계를 묶은 압축 단위 |
| coverage | 어느 원문 메시지가 활성 압축 블록에 포함되는지 나타내는 범위·집합 관계 |
| tier / generation | tier는 요약 계층 1·2·3, generation은 young·old 상태. 서로 다른 축이다 |
| 파생 뷰 | 저장한 원문·상태에서 목적에 맞게 만든 표시 결과. 모델 입력용 뷰와 저장 상태는 같지 않다 |
| nudge | 모델에게 압축을 고려하도록 알리는 안내. 안내 발생과 실제 압축 성공은 별개다 |
| preflight | 상류 전송 전에 용량과 실행 가능성을 확인하는 사전 검사 |
| 프롬프트 접두부 캐시 | 이전 입력과 같은 앞부분을 제공자가 재사용하는 장치. 앞부분의 변화가 비용에 영향을 줄 수 있다 |
| 도구 호출 쌍 | 호출과 그 결과의 연결. 호출만 지우거나 결과만 남기면 API의 대화 형식을 깨뜨릴 수 있다 |
| 서명된 추론 데이터 | 본문과 서명의 대응을 지켜야 하는 데이터. 재전송 과정에서 임의 정리가 안전하지 않다 |
| CCR | 큰 도구 결과 원문을 저장하고 참조로 다시 조회하는 기능 계열. 요약과 원문 보관의 역할을 구분한다 |
| BM25 / fuzzy | 단어 관련성을 계산하는 점수 / 철자가 일부 다르거나 조각이 겹치는 정도를 보는 점수 |
| 지연 로딩 | 필요한 시점까지 읽기를 미루는 방식. 적게 읽는 장점과 읽기 실패 처리의 책임이 함께 생긴다 |
| cold load | 메모리에 없는 세션을 저장소에서 처음 불러오는 과정. 프로세스 재시작 뒤의 복구와 연결된다 |
| 원자적 공개 | 완성된 새 상태를 한 번에 보이게 하는 전략. 이 저장소의 파일 경로에서는 임시 파일과 이름 변경을 쓴다 |
| 멱등성 | 같은 처리를 반복해도 한 번 처리한 결과와 같아야 하는 성질. 태그·형식 정리의 검증에 유용하다 |
| SMT · 반례 | 조건을 수식으로 주고 만족하는 경우를 찾는 방법과, 원하는 성질을 깨뜨리는 입력 사례 |
| 점진 배포 | 일부 설치부터 새 기능을 켜는 방식. 코드가 존재해도 기본 배포 비율이 0이면 일반 경로와 다를 수 있다 |
다음 링크는 본문의 개념을 더 공부할 때 볼 공식 문서입니다. 패키지 버전과 실행 지원 조건은 본문의 분석 기준 소스 및 해당 버전 문서와 함께 확인하세요.
| 항목 | 현재 상태 |
|---|---|
| 선정 | GitHub Trending TypeScript Weekly #16 · 2026-10-23 분석 |
| 스타 / 포크 | 2,452 / 210 — 2026-10-23 GitHub 기준 |
| 생성일 / 주 언어 | 2026-08-06 / TypeScript — 2026-10-23 GitHub 기준 |
| 최신 릴리스 | v0.1.204 · 2026-10-21 — 2026-10-23 GitHub 기준 |
| 최근 push | 2026-10-23 — GitHub 기준 |
| 분석 기준 커밋 | fdbddd0 · 2026-10-23PR #4373 병합, 원문 메시지: Merge pull request #4373 from z0d0dOr/2026-10-22_orphan-cold-load-recovery |
| 패키지 버전 | 본체 0.1.204 / acp-kernel 0.0.160 / pi-subagents 0.2.2 |
| 코드 규모 | TypeScript 466,088줄 / 1,395파일. 테스트·설정·선언 포함, 빈 줄·주석 포함 |
| 테스트 구분 | TypeScript 테스트 경로·파일 303,709줄 / 그 외 구현·설정·선언 162,379줄. *.test.ts 1,013개 |
| 그 밖의 언어 | JavaScript 계열 6,861줄, Python 1,554줄, HTML 711줄, CSS 281줄, Shell 33줄 |
| 라이선스 메타 | 2026-10-23 GitHub API 분류 NOASSERTION, package.json 선언 MIT |
| 실제 라이선스 파일 | MIT 본문 뒤에 최종 사용자에게 보이는 제품·문서 등의 출처 표시를 요구하는 추가 조건이 있다 |
| 성능 수치의 성격 | 100K 창·5배 절감·캐시 적중률 95~97%는 README 기준 프로젝트 측 설명. 보편적인 성능 보증으로 해석하지 않는다 |
요약은 원문과 다릅니다. 빠진 숫자·예외·결정 이유는 저장한 원문을 다시 찾아야 합니다. 원문·세션 저장소를 지우거나 읽을 수 없으면 복원 기능의 전제도 달라집니다.
호스트마다 지원 범위가 다릅니다. README는 Codex의 MCP 도구 설치만으로 모델 트래픽까지 자동 연결되지 않는다고 설명합니다. 별도 확장과 네이티브 압축을 동시에 켜는 경우도 주의해야 합니다. 기본 배포 비율이 0인 기능을 일반 동작으로 가정하지 마세요.
코드만으로 성능을 보장할 수는 없습니다. README의 품질·절감률·장기 안정성 설명은 모델·작업·세션 조건에 따라 달라지며, 모든 환경에서 같은 결과를 보장하지 않습니다.
라이선스 표기에는 차이가 있습니다. GitHub 분류, 패키지의 MIT 선언, 실제 LICENSE의 추가 출처 표시 조건을 모두 확인해야 합니다. 단순한 MIT 표기만으로 파일의 조건 전체를 설명할 수 없습니다.
데이터는 계속 쌓입니다. 대화·도구 출력에 포함된 내용이 원문 저장과 진단 자료에 남을 수 있습니다. 저장 경로·보존 정책·선택적 암호화의 적용 범위는 실제 설정과 저장 방식에 맞춰 확인하세요.