1.4k 스타 · MIT · TypeScript 단일 코드베이스. Claude Code · Cursor · Aider가 "어떤 모델이든 붙일 수 있는 범용 에이전트"를 지향할 때, Reasonix는 정반대로 갔다 — DeepSeek API의 "접두사 캐시(prefix-cache)"가 한 번이라도 깨지면 비용이 10배가 되는 구조에 맞춰, 시스템 전체를 "캐시가 깨지지 않는 방향"으로 설계한 철저히 편향된 에이전트다. 실 사용자 하루 데이터: 4.35억 입력 토큰 · 캐시 적중률 99.82% · 청구액 $1.38 (캐시 없으면 $61.06).
Anthropic API를 부르는 Claude Code, OpenAI 모델을 끼우는 Cursor, 아무 모델이나 붙일 수 있는 Aider와 다르게 — Reasonix는 의도적으로 DeepSeek 한 종류만 지원한다. 그 대가로 얻는 것은 "긴 작업을 켜둬도 청구서가 부담스럽지 않은" 비용 곡선이다.
패키지명은 npm reasonix, 별칭(bin)으로 dsnix도 등록되어 있다. Node 22+가 깔린 어디서든 npx reasonix code 한 줄로 떠서 동작한다. macOS · Linux · Windows(PowerShell·Git Bash·Windows Terminal) 모두 지원.
2026년 5월 현재 GitHub에는 "Claude Code 클론", "Cursor 대안", "Aider 포크"가 셀 수 없이 많다. 그 사이에서 Reasonix가 단숨에 TrendShift 상위권으로 올라온 이유는 명확하다 — 한 가지 모델 패밀리만 깊게 파고든 첫 에이전트이기 때문이다.
대부분의 에이전트 프레임워크는 모델을 추상화한다. --model 플래그를 받고, OpenAI든 Anthropic이든 OpenRouter든 다 끼울 수 있게 한다. 그 결과 모든 모델에 "최소공배수"로만 동작한다.
Reasonix는 정반대 — DeepSeek의 캐시 동작, R1 사고 토큰의 형태, 툴콜이 깨지는 패턴 모두를 시스템 설계의 전제(invariant)로 박아넣었다. README의 표현 그대로 "Cache stability isn't a feature you turn on; it's an invariant the loop is designed around."
일반 에이전트는 "어떤 차에든 맞는 범용 타이어"를 만드는 회사. F1 코너에 들어가면 미끄러진다.
Reasonix는 "DeepSeek 전용 슬릭 타이어". 다른 트랙에선 못 쓰지만, DeepSeek 코너에선 다른 어떤 타이어보다 빠르고 마모(=비용)가 적다.
TUI 상단 바에 turn $0.003, session $0.12이 항상 떠 있다. 0.05달러 넘어가면 노랑, 0.20달러 넘어가면 빨강. "내가 지금 얼마짜리 답을 받고 있는지"가 매 턴 보인다. 비용 인지(price-aware) UX를 처음부터 박아둔 코딩 에이전트는 아직 드물다.
Claude Code는 클로즈드, Cursor도 클로즈드. Reasonix는 MIT 라이선스의 진짜 오픈소스다 — 포크해서 사내용으로 커스터마이즈하거나, 회사 내부 LLM 게이트웨이에 붙일 수 있다.
본체는 Ink(터미널에서 React를 쓰는 라이브러리) 기반 TUI지만, dashboard/ 디렉토리에 별도의 웹 대시보드도 들어있다. 세션 로그, 캐시 히트율, 비용 추이를 브라우저에서 볼 수 있다.
package.json을 열면 한눈에 보인다. 94.6%가 TypeScript, 나머지는 CSS(4.3%, 대시보드용) + 소량 기타. 즉, 백엔드(LLM 호출 루프) · 프론트(터미널 UI) · 빌드 스크립트가 모두 한 언어로 묶여 있다.
| 라이브러리 | 역할 | 왜 이걸 골랐나 |
|---|---|---|
commander | CLI 인자 파싱 | Node 생태계 표준. 학습 곡선 거의 없음 |
ink 7.x | 터미널에 React를 렌더링 | App.tsx · LiveRows.tsx · StatsPanel.tsx 모두 React 컴포넌트 |
react 19.2 | Ink가 사용하는 렌더 엔진 | 훅(hook) 기반 상태관리를 그대로 활용 |
undici | Node용 고성능 HTTP 클라이언트 | DeepSeek API SSE 스트림 처리 |
eventsource-parser | SSE 청크 파싱 | 스트리밍 토큰을 한 글자씩 받기 |
web-tree-sitter | 코드 파서 | 의미 단위 검색(semantic_search) · 인덱싱 |
zod 4.x | 런타임 타입 검증 | 모델이 뱉은 JSON이 스키마와 맞는지 검사 |
ws | WebSocket | 대시보드와 양방향 통신 |
ignore · picomatch | .gitignore 패턴 매칭 | 파일 인덱싱 시 무시할 경로 처리 |
undici로 SSE를 받고 eventsource-parser로 파싱한다.일반 웹 React가 DOM에 그림을 그린다면, Ink는 터미널 출력에 그림을 그린다. <Box>, <Text> 같은 컴포넌트가 각각 줄·문자에 매핑된다. useState, useEffect 그대로 쓰면 된다.
레포의 dashboard/ 폴더는 독립된 npm 워크스페이스다. npm run build:dashboard로 따로 빌드되고, 최종 결과물(dashboard/dist/)은 npm 패키지에 함께 들어간다. 대시보드는 Preact + HTM 기반 SPA로 구성되며, 세션 로그와 비용 추이를 WebSocket으로 실시간 표시한다.
| 도구 | 역할 |
|---|---|
tsup | TypeScript → 최종 번들. dist/cli/index.js 한 파일로 출력 |
biome | 린트 + 포맷터 (ESLint + Prettier 대체) |
vitest | 유닛 테스트 + 커버리지 |
@stryker-mutator | 변이 테스트(Mutation Testing) — 테스트가 진짜 잘 짜여졌는지 검증 |
simple-git-hooks | pre-commit · pre-push 훅. 푸시 전에 verify 강제 실행 |
>를 <로 바꾸기) 테스트가 그 버그를 잡아내는지 확인한다. 잡아내면 통과, 못 잡으면 "테스트가 부실하다"는 신호. Stryker가 이 자동화를 해준다.공식 ARCHITECTURE.md는 시스템을 세 개의 기둥(pillar)으로 정리한다. 각 기둥은 "범용 에이전트 프레임워크라면 보지도 못할 문제"를 해결한다 — 왜냐하면 그것들은 DeepSeek이라는 특정 모델의 캐시 동작 위에서만 의미가 있는 문제들이기 때문이다.
문제: DeepSeek은 캐시된 입력 토큰을 원가의 ~10%로 청구한다. 하지만 자동 prefix 캐시는 이전 요청의 바이트 접두사가 정확히 동일할 때만 발동한다. 대부분의 에이전트 루프는 매 턴 컨텍스트를 재정렬·재작성·타임스탬프 주입을 하기 때문에, 실측 캐시 적중률이 20%도 안 된다.
해법: 컨텍스트를 세 영역으로 엄격히 분리한다.
일반 채팅 UX는 메신저 같다 — 답장하면 위로 스크롤하면서 메시지를 편집·재정렬할 수도 있다. 한 번 편집이 들어가면 그 위까지 전부 "이전과 다른" 메시지가 된다.
Reasonix의 로그는 회계 장부 같다 — 한 줄 쓰면 절대 지우지 않고, 늘 마지막에만 새 줄을 추가한다. 그래야 1번 줄부터 N-1번 줄까지가 항상 어제와 같은 모양이고, DeepSeek 서버가 "어 어제 본 거랑 같네, 캐시 쓰자"고 판단할 수 있다.
측정 지표: 매 턴마다 prompt_cache_hit_tokens / (hit + miss)가 계산되어 TUI 상단바에 표시된다. "이 디자인 선택이 정말 효과가 있는가"를 사용자가 매 호출마다 눈으로 본다.
문제: DeepSeek에서 실제로 자주 발생하는 실패 패턴들 —
<think> 블록 안에 갇힘모델이 "이제 read_file을 호출해야겠다" 하고 사고 영역에 JSON을 적었지만, 최종 메시지의 tool_calls 필드에는 빠뜨림. 일반 SDK는 그냥 "툴콜 없음"으로 처리하고 끝.
스키마가 깊거나 leaf 파라미터가 많으면(>10) 모델이 절반만 채우고 보내는 일이 잦다.
같은 파일을 30번 연속으로 read_file 하려는 무한 루프성 폭주.
응답 길이 제한에 걸려 { "path": "src/foo. 같이 끊긴 토큰콜.
해법: 네 단계 파이프라인.
| 단계 | 역할 |
|---|---|
flatten | 스키마가 >10 leaf params 또는 깊이 >2면 자동으로 dot-notation(user.address.city)으로 펴서 모델에 노출. dispatch()에서 다시 중첩 객체로 복원. |
scavenge | regex + JSON 파서로 reasoning_content를 뒤져 모델이 깜빡한 툴콜을 회수. |
truncation | 닫히지 않은 중괄호를 감지해서 자동으로 닫거나, 이어쓰기 요청을 보낸다. |
storm | 슬라이딩 윈도우 안에서 동일 (tool, args) 튜플 반복을 감지 → 호출 억제 + 반성(reflection) 턴 주입. |
문제: 프론티어 모델(v4-pro)을 기본으로 두고 툴 결과 전체를 컨텍스트에 누적하면, 활성 사용자 월 청구액이 $150~$250로 치솟는다. 대부분의 턴은 프론티어 추론이 필요 없고, 대부분의 세션은 한 번만 쓸 툴 결과를 계속 재전송하면서 돈을 태운다.
해법: 네 가지 보완 메커니즘.
| 프리셋 | 모델 | Effort | 비용비율 |
|---|---|---|---|
flash | v4-flash | max | 1× |
auto (기본) | v4-flash → v4-pro (hard turn) | max | 1–3× |
pro | v4-pro | max | ~12× |
"비싼 모델 + 깊은 추론"이 기본이 아니라 명시적 선택지다. 부가 호출(요약·서브에이전트·툴콜 수리)은 사용자의 프리셋과 무관하게 항상 flash + high로 하드코딩 — "툴 결과 산문 정리" 작업에 pro 요금을 낼 이유가 없다.
한 턴이 끝날 때, 로그 안의 모든 툴 결과 중 TURN_END_RESULT_CAP_TOKENS(3000)를 초과한 것은 그 토큰 수로 잘려 요약본으로 대체된다. "그 턴에 모델은 전체 텍스트를 봤다, 다음 턴부터는 압축본만 보고 필요하면 다시 읽으면 된다"는 합리적 트레이드오프.
/model)/model flash 또는 /model pro로 모델을 전환하면, 다음 변경까지 해당 모델이 유지된다 (sticky). /pro 단발 무장은 v0.50.0에서 제거되었으며 v0.51.0에는 존재하지 않는다.
현재 턴이 프론티어 추론이 필요하다고 모델이 판단하면, 답변 첫 줄에 <<<NEEDS_PRO>>> 또는 <<<NEEDS_PRO: reason>>>를 출력한다. 시스템이 이를 감지해 그 턴을 v4-pro로 재실행하며, 화면에 노란색 경고 줄로 사용자에게 표시 — "조용히 비싼 모델 쓰는" 짓은 절대 안 한다.
비용이 자동으로 올라가는 모든 사건은 반드시 사용자가 본다. 청구서를 열어보고 놀라는 일이 없도록 "조용한 자동 승급"은 검토 후 명시적으로 거부됨.
모든 /명령은 handlers/ 아래 주제별 파일로 쪼개져 있다 — 한 파일이 200줄을 넘지 않는다는 규칙이 있다. 새 슬래시 커맨드 추가 = 한 핸들러 파일 + 레지스트리 한 줄 수정.
이론적으로는 "이전 프롬프트 앞부분을 그대로 다시 보내라" 한 줄이면 끝나는 일이지만, 실제로는 — 시스템 프롬프트에 현재 시각을 주입하던 코드, 도구 목록을 alphabetical 정렬로 정규화하던 코드, 매 턴 컨텍스트를 압축하던 코드가 전부 캐시를 깨는 범인이다.
배울 것: loop.ts와 memory.ts를 읽으면서 "이 구현은 정확히 어떤 변화를 막고 있는가"를 추적해보면, "캐시 친화적 프롬프트 설계" 감이 잡힌다.
<Box flexDirection="column">, <Text color="green"> — DOM 대신 ANSI 시퀀스로 출력되는 것 외에 React 그대로다. 훅·상태·이펙트가 전부 동작한다.
실습: App.tsx의 useSubagent, useEditHistory 같은 커스텀 훅을 읽어보자. "터미널 위의 React"가 일반 SPA와 어떻게 다른지 감이 온다.
Reasonix의 edit_file은 모델에게 "이 문자열을 찾아서 이걸로 바꿔" 형식의 패치를 요구한다. 라인 번호 기반 diff는 모델이 자주 틀린다 — 한 줄 추가되면 모든 번호가 어긋난다. 문자열 매칭은 컨텍스트 자체가 정답을 강제한다.
매칭 실패는 FAILURE_ESCALATION_THRESHOLD 카운터를 올려 자동 승급의 신호로도 쓰인다 — 단일 메커니즘이 편집 + 비용 통제 두 곳에 동시에 기여하는 예쁜 설계.
src/mcp/는 Anthropic이 표준화한 MCP의 클라이언트 측 구현이다. stdio · SSE · Streamable HTTP 세 가지 전송 방식을 모두 지원한다. MCP 서버를 직접 작성해본 적 있다면, 클라이언트 쪽 코드를 읽는 게 큰 학습이 된다.
reasonix index는 로컬 Ollama(또는 임의의 OpenAI 호환 임베딩 엔드포인트)로 코드를 청크 단위 임베딩해서 의미 검색을 만든다. tree-sitter는 단순 라인 분할이 아닌 구문 트리 기반으로 함수 · 클래스 단위로 자른다. 6개 언어(Go · Java · JS · Python · Rust · TypeScript) 문법을 함께 로드한다.
대부분의 코딩 도구는 비용을 결제 페이지에 숨긴다. Reasonix는 매 턴 헤더에 색깔 뱃지로 띄운다. "사용자에게 자기 행동의 가격을 즉시 보여주는" 설계 패턴은 LLM 시대의 UX 키워드 중 하나가 될 것이다.
engines 필드에 명시됨. 22 미만이면 npx reasonix code가 거절한다.reasonix index로 의미 검색을 활성화하려면 Ollama 또는 OpenAI 호환 임베딩 엔드포인트가 필요.reasonix index로 대형 레포를 임베딩하면 그 임베딩 엔진(예: Ollama)의 GPU/RAM이 필요해진다."Reasonix는 유료 DeepSeek API 키가 반드시 필요하다. 완전 오프라인 또는 zero-cost 운용은 Aider + Ollama 또는 Continue를 보라."
Node 22+가 있는 머신에서 빈 폴더 하나 만들고 npx reasonix code 실행. 처음에는 DeepSeek API 키를 한 번 붙여넣으면 자동 저장. "Hello, 이 폴더 안에 hello.py 라는 파일에 print('hi')를 적어줘"부터 시작해보자.
관찰 포인트: 상단바의 cache: 0% → 80%+, 비용 뱃지의 변화. 같은 세션 안에서 두 번째 · 세 번째 요청을 보낼 때 캐시 적중률이 어떻게 올라가는지.
/skill new로 첫 스킬 만들기/skill new commit-message을 치면 <project>/.reasonix/skills/commit-message.md가 생성된다. frontmatter에 description:을 적고, 본문에 "git diff를 보고 Conventional Commits 형식으로 메시지를 뽑는 절차"를 자연어로 적어보자.
관찰 포인트: runAs: subagent로 바꿨을 때 격리된 하위 루프가 어떻게 동작하는지. 메인 컨텍스트에 잡음이 안 묻는다는 것이 핵심.
같은 작업을 두 세션으로 나눠 실행 — (A) 평범하게, (B) 매 턴 시스템 프롬프트에 --system "현재 시각: $(date)" 같은 동적 내용을 강제로 주입.
reasonix stats로 두 세션의 캐시 히트율과 누적 비용을 비교해보자. 같은 작업이 몇 배 더 비싸지는지 직접 측정.
repair/ 파이프라인 코드 읽기src/repair/index.ts를 출발점으로 scavenge → flatten → truncation → storm 순서로 각 파일을 읽으면서, "이 단계가 발동하는 정확한 조건"을 주석에 적으며 따라가보자. 그리고 tests/repair/*.test.ts를 보면 각 케이스의 실제 입력/출력 예시가 있다.
심화: 새로운 실패 케이스를 하나 만들어 패치를 PR로 보내는 것까지 — good first issue 라벨에 어울리는 작업.
README는 "OpenAI 호환 shim이 --model 오버라이드로 동작은 한다, 테스트되진 않았다"고 한다. 이 한 줄을 실험해보자 — src/client.ts를 베이스로 Gemini나 GPT-5용 클라이언트 어댑터를 작성하고, 같은 워크로드를 돌렸을 때 캐시 동작이 어떻게 달라지는지 측정.
결과는 둘 중 하나: "다른 백엔드도 잘 동작 → Pillar 1의 캐시 가정은 사실상 일반화 가능했다" 또는 "역시 DeepSeek 외에서는 캐시 모델이 달라 손익이 안 맞는다 → 비목표가 정당화됨". 어느 쪽이든 LLM API 디자인에 대한 깊은 인사이트를 얻는다.
OpenAI API의 chat completion 호출과 SSE 응답 구조, system/user/assistant/tool 메시지의 역할, tool_calls 필드의 형식. DeepSeek의 가격 페이지를 직접 읽고, "캐시 hit vs miss 단가"를 비교해보기. "왜 캐시가 본질인가"를 숫자로 납득해야 다음 주차가 의미 있다.
ReAct(Reasoning + Acting), 툴 호출 루프, 멈춤 조건(stop condition) 등 일반 에이전트 패턴. 그 다음 src/loop.ts를 열어 Reasonix는 어디가 일반적이고 어디가 다른가를 표로 정리해보자.
Ink 공식 튜토리얼로 간단한 TUI 한 개 만들기. 그 후 cli/ui/App.tsx의 훅(useEditHistory · useSubagent · useCompletionPickers)을 한 개씩 분리해 읽기. 1984 LOC의 거대한 컴포넌트가 어떻게 책임을 쪼개 관리하는지가 보인다.
자신만의 작은 에이전트를 OpenAI 또는 Anthropic API로 작성하되, Pillar 1의 세 영역 분리(immutable prefix / append-only log / volatile scratch)를 그대로 모방해보자. 그리고 캐시 hit율을 측정해 본가(Reasonix)의 99.82%에 얼마나 근접할 수 있는지 도전.
reasoning_content 필드에 분리되어 들어온다. 공식적으로 "다음 턴에 다시 보내지 말 것" 권고.repair/ 모듈은 이 JSON이 깨졌을 때 자동 복구한다.vadimdemedes/ink 라이브러리. Claude Code · GitHub Copilot CLI 등 다수의 AI CLI가 채택.src/repair/storm.ts만 읽고, "동일 호출 폭주 감지" 로직을 자기 언어(파이썬·고·러스트)로 재구현해본다. 50줄짜리 코드지만 슬라이딩 윈도우 · 인자 해시 · 반성 턴 주입까지 압축적으로 들어있다.