본문으로 건너뛰기

"search" 태그 — 2개 게시물

모든 태그 보기

AutoRAG Agent: 문서와 메신저 검색부터 출처 기반 답변까지

· 약 21분
신승엽
AI Research Engineer, Braincrew

AutoRAG Agent: Search agent that finds anything in your computer

AutoRAG는 문서 폴더를 등록하고 자연어로 질문하면 관련 내용을 찾아주는 오픈소스 도구다. 현재 2.x 버전인 AutoRAG Agent는 HWP, PDF, DOCX 같은 파일을 검색할 수 있게 변환하고, 질문에 맞는 문서를 찾아 읽은 뒤 출처와 함께 답을 정리한다. 터미널에서 직접 쓸 수도 있고, Claude Code나 Cursor에 검색 도구로 붙일 수도 있다.

공식 README는 AutoRAG Agent를 사용 이력과 피드백을 참고하는 문서 검색 에이전트로 소개한다. 로컬 파일과 연결된 메일·메신저를 함께 검색하므로, 자료를 어느 앱에 저장했는지 몰라도 내용으로 찾을 수 있다.

기존 AutoRAG와의 차이

AutoRAG는 원래 데이터에 맞는 RAG 파이프라인을 자동으로 찾아주는 Python 도구였다. 2.x부터는 문서를 검색하고 답변을 작성하는 에이전트로 개발되고 있으며, 기존 파이프라인 최적화 도구는 legacy/에서 별도로 유지보수한다.

AutoRAG로 할 수 있는 일​

AutoRAG Agent의 검색 대상, 관련 내용 탐색, 원문 확인, 출처가 붙은 답변으로 이어지는 full 모드 동작 예시

제안서, 회의록, 기술 노트가 여러 폴더에 흩어져 있다고 하자. 찾고 싶은 내용은 기억나지만 파일명이나 문서에서 썼던 표현은 기억나지 않을 수 있다. AutoRAG에서는 검색할 폴더를 등록한 뒤 "지난 회의에서 리스크 상태를 어떻게 바꾸기로 했지?"처럼 질문한다. 검색 결과만 반환하는 모드는 Lite, 대규모 언어 모델(LLM)이 문서를 읽고 답변까지 작성하는 모드는 full이다.

내용 기반 검색​

AutoRAG는 문서를 검색 가능한 크기의 텍스트 조각, 즉 청크로 나누고 인덱스를 만든다. 임베딩은 이 텍스트를 의미 비교에 사용할 숫자 벡터로 바꾸는 과정이다. 검색할 때는 단어가 일치하는 정도를 보는 키워드 검색과, 질문과 내용의 의미가 가까운지 보는 벡터 검색을 활용한다. 두 검색의 순위를 합치는 방식이 hybrid 검색이다.

문서에 적힌 표현을 정확히 기억하지 못해도 질문의 의미로 관련 내용을 찾을 수 있다. 다만 코드나 식별자처럼 정확한 문자열을 찾는 질문에서는 벡터 검색이 원하는 결과를 뒤로 밀어낼 수 있다. 실제 검색 순위는 뒤의 Lite 실험에서 비교했다.

HWP·PDF·엑셀 지원​

AutoRAG는 HWP/HWPX, PDF, DOCX, PPTX, XLSX 등의 본문을 마크다운으로 변환한다. 원본 파일은 원래 폴더에 남고, 변환한 텍스트를 검색에 사용한다.

사업비가 HWP 제안서의 표에 있고 세부 내역은 XLSX에 있다면, 인덱싱 단계에서 두 파일을 변환해 함께 검색할 수 있게 한다. HWP의 표 안 텍스트나 XLSX의 행 구조가 변환 결과에 남는지도 확인할 필요가 있다.

지원 형식이라고 모든 파일을 읽는 것은 아니다. 정부 양식 HWP 일부는 파싱에 실패했고, 이미지로만 된 PDF는 기본 설정에서 텍스트를 추출하지 못했다. 사용할 문서가 제대로 변환되는지 먼저 확인해야 한다.

답변 생성과 출처 확인​

full 모드는 검색으로 찾은 후보 문서를 에이전트가 직접 열어보고, 질문에 필요한 내용을 항목별로 정리한다. 답과 함께 출처 경로를 주므로 원문을 다시 확인할 수 있다. 공식 README는 이 검색, 원문 확인, 답변 작성 과정을 AutoRAG Agent의 기본 동작으로 설명한다.

같은 주제를 다룬 문서가 여러 개라면 각 문서에서 관련 내용을 모아 답변을 구성한다.

검색 스킬 문서에 따르면 autorag evidence로 이전 검색의 원문 발췌, 출처, 검색 방식 등을 확인할 수 있다. 답변에 적힌 요약만으로 판단하기 어려울 때 원문과 대조하는 용도다. 이 명령은 이번 실험에서는 직접 검증하지 않았다.

원본 파일의 이름이나 폴더 구조는 바꾸지 않는다. 출처가 붙은 답변도 확인은 필요하다. 긴 업무 문서에서는 필요한 내용을 놓치거나 원문과 다른 표현으로 답한 사례가 있었다.

검색 메모리와 피드백​

README에 따르면 메모리에 이전 검색에서 유용했던 방법을 기록하고, 비슷한 질문에 답할 때 참고한다. 사용자는 번호가 붙은 답변 항목에 유용했는지 아닌지를 피드백할 수 있다. 공식 아키텍처 설명에서 말하는 self-evolving은 이 메모리와 피드백 동작을 가리킨다.

다만 이번 실험에서 +1과 -1 피드백을 준 뒤 다시 검색했을 때 결과 순위는 같았다. 확인한 구현에서는 full 모드의 LLM에 주는 힌트로 쓰였으며, 반복 사용만으로 검색 정확도가 높아지는지는 검증하지 못했다.

Claude Code 연동​

이미 Claude Code나 Cursor로 일하고 있다면 Lite 모드를 검색 도구로 붙일 수 있다. AutoRAG가 문서 변환과 검색을 맡고, 기존 에이전트는 반환된 청크를 읽어 답변을 작성한다.

예를 들어 Claude Code에 제안서 내용을 확인해 달라고 요청했을 때, 파일 형식별 변환 명령을 매번 고르게 하는 대신 준비된 인덱스에서 관련 내용을 찾게 할 수 있다. 저장소에는 이 용도의 설치·검색 스킬이 포함돼 있다. 이 글에서도 Lite 스킬을 붙인 Claude Code와 grep 및 변환기만 쓰는 Claude Code를 비교했다.

Lite 자체는 답변 생성용 LLM을 호출하지 않고 임베딩도 로컬에서 처리한다. 다만 검색 결과를 Claude Code 같은 외부 에이전트에 넘기면, 그 내용은 해당 에이전트의 모델 입력으로 사용된다.

증분 인덱싱과 중복 제외​

폴더에 문서를 추가하거나 내용을 수정한 뒤에는 refresh로 인덱스를 갱신한다. 인덱싱을 담당하는 MinSync가 바뀐 청크만 다시 임베딩하므로, 기존 문서 전체를 매번 처음부터 처리하지 않는다.

중복 판별 도구 dupey는 본문이 완전히 같은 문서를 찾아 인덱싱 대상에서 제외한다. 원본 파일을 삭제하는 동작은 아니며, 어떤 파일을 남길지는 수정 시각으로 정한다. 따라서 방금 복사한 파일이 원본보다 우선될 수도 있다.

메일·메신저 연동​

문서 폴더 외에 Slack, Notion, 메일, 카카오톡 등을 연결하는 기능도 제공한다. 회의록과 메신저의 후속 논의를 함께 찾고 싶을 때 사용할 수 있다.

여러 소스를 연결할 때도 각 도구가 관리하는 저장소와 검색 기능을 이용한다. 예를 들어 Slack은 slacrawl의 아카이브를, Obsidian은 qmd를 사용한다. 외부 자료 전체를 AutoRAG 전용 DB로 옮길 필요는 없다. 다만 로컬 문서는 검색용 텍스트와 인덱스를 만들고, 클라우드 드라이브 연동에는 로컬 미러가 사용되므로 모든 연결이 복사 없이 동작한다는 뜻은 아니다. 데이터소스 문서에 소스별 저장·검색 방식이 정리돼 있다.

연동하려면 데이터소스별 CLI나 로컬 아카이브를 준비해야 한다. 소스마다 지원하는 검색 방식도 다르다. 예를 들어 Slack과 Notion은 어휘 검색을 사용한다. 외부 데이터소스 연동은 이번 실험에서 직접 검증하지 않았으며, 아래 연동 표는 v2.5.2 공식 문서를 기준으로 정리했다.

데이터소스백엔드검색 방식
카카오톡katok CLIBM25, 시맨틱, hybrid
메일 (Gmail/IMAP/Maildir)mailcrawlBM25, 시맨틱, hybrid
로컬 메일 내보내기내장 .mbox / .eml 파서어휘
RSS·뉴스HTTP 폴링어휘
ObsidianqmdBM25, 시맨틱
DiscorddiscrawlBM25, 시맨틱, hybrid
Slack, Notion, WhatsApp, Telegramslacrawl, notcrawl, wacrawl, telecrawl어휘(FTS5)만
GitHubREST APIIssues/PR, 어휘
클라우드 드라이브rcloneGoogle Drive, OneDrive, Dropbox
사진·스크린샷clawgalleryOCR + 비주얼
macOS Spotlightmdfind시스템 인덱스

BM25와 FTS5는 어휘 검색, 시맨틱은 의미 기반 검색을 가리킨다.

Lite와 full 모드​

Lite는 문서 청크를 기존 에이전트에 전달하고, full은 설정한 LLM으로 원문을 확인해 최종 답변을 작성한다

사용 순서는 검색할 폴더 등록, refresh로 인덱스 생성, 질문 입력이다. 검색 결과를 기존 에이전트에서 활용하려면 Lite, AutoRAG에서 답변까지 받으려면 full을 사용한다.

모드입력과 결과쓰는 상황
Liteautorag lite retrieve "질문"으로 관련 문서의 청크를 JSON으로 받는다Claude Code나 Cursor가 검색 결과를 읽고 답을 만들 때
fullautorag search "질문"으로 출처가 붙은 답을 받는다AutoRAG 안에서 검색부터 답변 작성까지 처리할 때

사용자가 청커, 임베더, 벡터 DB를 직접 선택할 필요는 없다. 임베딩 모델(Qwen3-Embedding-0.6B)은 로컬에서 실행한다. full 모드는 Pi 에이전트 프레임워크로 구현했으며, 설정한 모델 하나가 검색부터 답변 작성까지 맡는다. 외부 LLM을 설정하면 해당 서비스의 인증 정보가 필요하고, 답변에 사용하는 문서 내용이 그 서비스로 전달된다. 코드에는 로컬 모델을 사용하는 경로도 있다.

AutoRAG Agent의 세 가지 원칙: Search in place, Just works, Built for speed

CLI·TUI·라이브러리​

단발성 질문은 CLI로 보내고, 터미널에서 대화하며 사용하려면 autorag tui를 실행한다. TUI는 베타다. TypeScript 라이브러리도 제공하므로 애플리케이션에서 검색을 호출하고 답변과 출처를 받아 사용할 수 있다. README의 사용 예시에서 두 방식을 확인할 수 있다.

다른 사용자와 검색을 공유하는 SimpleX 기반 P2P 기능도 있다. 별도로 활성화하고 상대방 신뢰, 질의 승인, 공유 정책을 설정하는 방식이다. 이 글에서는 CLI와 에이전트 스킬을 사용했으며, TUI·라이브러리·P2P 연동은 직접 테스트하지 않았다.

MCP 서버는 제공하지 않는다.

내부 구성​

앞선 연재에서 다룬 MinSync, jikji, dupey는 AutoRAG의 문서 처리 단계에 연결돼 있다. autorag refresh를 한 번 돌리면 문서 변환부터 검색 인덱스 생성까지 순서대로 실행된다.

autorag refresh
-> 파서: HWP/HWPX, PDF, DOCX, PPTX, XLSX, EML, 텍스트를 마크다운으로 변환해 .autorag/parsed/ 에 미러링
-> dupey: 본문 해시가 같은 exact 중복 중 최신 파일 하나만 남기고 제외
-> MinSync: 바뀐 청크만 임베딩, BM25 + 벡터 인덱스를 LanceDB에 유지
-> jikji: 원본 폴더의 .jikji/ 에 파일 카드와 에이전트 맵 생성

refresh --json 진단에 duplicate-excluded가 찍히고, 워크스페이스에 .autorag/minsync/가 생기고, 원본 폴더에는 .jikji/가 생긴다. 이 폴더에 파일 카드와 에이전트 맵을 저장하며, 원본 파일은 수정하지 않는다. dupey 글에서 "최신 수정 시각 파일이 남는다"고 적었던 동작도 그대로였다. 원본 글을 cp로 복사했더니 방금 만든 사본이 인덱싱되고 원본이 빠졌다.

MinSync는 임베딩과 검색 엔진 자체를 맡는다. AutoRAG의 hybrid 검색은 MinSync 안에서 키워드·벡터 검색 순위를 RRF(k=60)로 합친다. jikji는 full 모드에서 jikji_find라는 도구로 에이전트에게 노출된다. agentdir만은 런타임에 연결돼 있지 않다. 원본과 에이전트용 뷰를 분리한다는 설계 관점을 공유할 뿐이다.

설치와 첫 검색​

bun install -g @autorag/librarian@2.5.2
autorag lite init --search-paths ~/Documents/reports --workspace ~/autorag-ws
autorag lite refresh --json
autorag lite retrieve "작년 제안서 사업비" --top-k 5 --json

Node 24 이상이 필요하다. PDF 파싱에는 Java 11 이상, jikji 컴파일에는 Rust 툴체인이 필요하다. 실험 환경에서는 첫 refresh에서 MinSync와 jikji를 cargo install로 빌드했다. MinSync는 사용 가능한 cargo가 있으면 빌드를 시도하고, 그렇지 않으면 배포 바이너리를 받는 경로도 있다. 블로그 글 23편짜리 폴더에서 첫 refresh가 753초였는데, 파싱이나 임베딩이 아니라 대부분 컴파일 시간이었다.

실험 대상과 비교 방법​

블로그 마크다운 23편, 개인 노트 마크다운 338편, 업무 문서 81개를 준비했다. 업무 문서에는 HWP, PDF, DOCX, PPTX, XLSX가 섞여 있다. 업무 문서 수는 단계마다 줄었다. 첫 인덱싱은 81개로 돌렸고, 이미지만 있어 0자로 파싱된 PDF 3개와 PPTX 2개를 제외했다. 남은 76개에서 NaN 임베딩 오류를 낸 논문 PDF 1개를 더 빼고, 75개가 남은 폴더로 검색 실험을 했다. 그 75개 중 파서 실패 6개, 크기 초과 1개, 중복 제외 3개를 빼고 실제로 검색 가능했던 문서는 65개다.

먼저 Lite 자체의 인덱싱 시간과 검색 순위를 확인했다. 이어서 같은 질문에 답하도록 Claude Code에 Lite 스킬을 붙인 경우와 grep·문서 변환기만 사용한 경우를 비교했다. 마지막으로 AutoRAG full 모드에서 모델별 답변을 비교했다. Lite의 검색 시간과 에이전트의 전체 답변 시간은 구분해서 봐야 한다.

명령과 실험 결과는 v2.5.2(2026-09-20) 기준이다. 소개 중 외부 데이터소스, TUI, 라이브러리, P2P 기능은 공식 문서를 참고했으며 직접 실험한 범위에는 포함하지 않았다.

Lite 검색 결과​

인덱싱·검색 시간​

폴더파일첫 refresh무변경 refresh파일 1개 수정 후retrieve
블로그 마크다운23개, 356KB753초 (컴파일 포함)2.4초19.6초1.2~1.4초
개인 노트 마크다운338개, 6MB파싱 + 임베딩 629초1.3초
업무 문서 (HWP/PDF/DOCX/PPTX/XLSX)81개, 337MB파싱 253초 + 임베딩 458초1.3초

업무 문서의 파일 수는 처음 준비한 81개 기준이다. retrieve 시간은 파일을 제외한 뒤 실제로 검색 가능했던 65개 문서를 대상으로 측정했다.

증분 refresh에서는 변경된 청크만 처리했다. 파일 하나를 고치고 사본 하나를 추가한 뒤 다시 돌리니 청크 3개만 다시 임베딩됐다.

노트 338개를 인덱싱할 때는 진행 상태를 알기 어려웠다. 임베딩 단계에서 10분 넘게 CPU 사용률이 0%여서 멈춘 줄 알고 프로세스를 두 번이나 죽였다. 스택을 찍어 보니 임베딩 계산이 아니라 MinSync 0.4.5가 LanceDB에 작은 커밋을 수천 번 하면서 디스크를 기다리는 시간이었다. lite status는 inFlight: true만 보여주고 진행률은 알려주지 않으니, 파일이 수백 개 이상인 폴더를 처음 인덱싱할 때는 이 상태만 보고 멈췄다고 판단하기 어렵다.

검색 품질​

문서의 표현을 바꿔 질문해도 관련 글을 찾았다. "변경된 파일만 다시 임베딩해서 인덱스를 최신으로 유지하는 방법"을 넣으면 MinSync 글이 1위로 나오고, 영어로 "how does the agent avoid repeated ls and grep loops"를 넣어도 jikji 글이 1위다.

반면 정확한 토큰을 찾는 질문에는 약했다. 0o444를 넣었더니 그 문자열이 없는 Oracle 글이 1위였고, 정작 0o444가 들어 있는 청크는 5위였다. 검색 확인용으로 글 끝에 추가한 "보라색 코끼리 프로토콜은 분기마다 인덱스를 재검증한다"라는 문장을 찾게 했을 때도 정답 청크는 2위에 그쳤다. 검색 방식과 점수 해석은 구분할 필요가 있다. 로컬 문서에 등록된 검색 방식이 벡터와 hybrid뿐이고 BM25 단독 경로는 없다(src/agent/agent.ts:437-443). 그리고 방식별로 min-max 정규화를 하기 때문에 1위 점수는 관련도와 상관없이 항상 1.000이 된다. 그래서 점수만 보고 "관련 문서가 없다"고 판단할 수가 없다.

Lite를 Claude Code에 붙이기​

저장소의 skills/autorag-lite-setup과 skills/autorag-lite-search 폴더를 프로젝트의 .claude/skills/에 복사하면 Claude Code가 autorag lite retrieve를 검색 도구로 쓰기 시작한다. README에는 이 설치 방법이 따로 없어서 직접 복사해야 한다. 스킬 문서의 lite report JSON 스키마 설명에는 필수 필드 두 개(results[].evidence, mapping[].content)가 빠져 있어서 문서대로 따라 하면 검증 오류가 나는데, 오류 메시지는 어느 필드가 문제인지 알려주지 않는다.

grep 에이전트와 비교​

같은 질문을 두 에이전트에게 줬다. 하나는 Lite 스킬만 쓰고, 다른 하나는 grep과 변환기만 쓴다. 둘 다 폴더를 처음 보는 상태였고, 질문은 문서에 나오는 단어를 일부러 피해서 바꿔 썼다.

폴더질문 수grep 에이전트Lite 에이전트
블로그 마크다운 23편66/6 정답, 도구 3회, 47초, 6.2만 토큰6/6, 9회, 100초, 10.4만
개인 노트 338편88/8, 7회, 115초, 6.8만8/8, 13회, 189초, 9.3만
업무 문서 75개66/6, 8회, 136초, 7.4만6/6, 15회, 183초, 9.5만

세 번 모두 정답률은 같았고, 시간과 토큰은 grep 쪽이 적게 들었다. 솔직히 예상 밖이었다. 338편 실험에서는 grep 에이전트의 첫 키워드가 세 번 빗나갔는데, 파일명을 단서로 문서를 찾았다. 이번처럼 파일명으로 내용을 짐작할 수 있는 폴더에서는 Claude Code와 grep만으로도 모든 질문에 답할 수 있었다.

HWP 변환에서는 Lite 쪽이 나았다. 업무 문서 실험에서 grep 에이전트는 hwp5txt가 표 안 텍스트를 버리는 바람에 HWP 하나를 다시 변환해야 했고, XLSX는 sharedStrings.xml을 뜯어서 열 이름만 겨우 읽었다. AutoRAG의 파서는 같은 HWP를 표까지 8,022자로 뽑았다. 다만 XLSX에서도 행 구조가 보존된다고 일반화할 수는 없다. 뒤의 후속 실험에서는 한국어 문자 참조와 행·열 관계가 제대로 복원되지 않은 파일을 확인했다.

Lite 검색 결과를 읽는 과정에도 불편한 점이 있었다. 검색 결과 top-5 JSON이 58KB나 됐고, 청크 안 어디에 답이 있는지 표시도 줄 번호도 없어서 6문항 중 5문항은 파일을 다시 열어야 했다. 청크가 표지와 머리말 쪽에 몰리다 보니 긴 문서의 중간 내용은 잘 올라오지 않았고, 무관한 문서 하나(AIPMO 1.5_산출물.docx)가 질문 4개에서 반복해서 상위에 나왔다. "공공기관" 설문을 물었는데 금융기관 설문이 1, 2위로 나온 일도 있었다.

설정 문제를 해결하는 과정에서는 벡터 검색의 효과를 확인할 수 있었다. MinSync를 AutoRAG를 거치지 않고 직접 돌렸더니, AutoRAG가 기록하는 임베딩 identity 파일이 없다는 이유로 벡터 검색이 별도 경고 없이 빠지고 hybrid만 돌았다. 그 상태에서는 가장 어려운 질문의 정답이 4위(점수 0.08)였는데, autorag lite refresh --method minsync로 identity를 기록하고 나니 같은 질문이 1위로 올라왔다.

full 모드의 답변 품질​

답변 생성과 인증​

full 모드는 Lite의 검색 결과를 LLM이 읽고 답으로 정리한다. 먼저 추론 기능을 끈 상태로 예비 답을 내고, 이어서 추론 기능을 켜고 내용을 검증한 뒤 최종 답을 낸다. 에이전트에게는 bash가 실제 셸로 주어지기 때문에 후보 문서를 cat으로 열어 확인할 수 있다. 최종 답은 항목별로 번호를 붙여 정리하고 출처 경로를 함께 제공한다.

이번 실험에서는 외부 LLM의 API 키를 환경변수로 제공했다. resolveRoleAuth에는 외부 제공자의 환경변수 인증과 로컬 런타임 인증 경로가 있다(src/cli/config.ts:1303). Claude Code나 Codex에 로그인돼 있다는 이유만으로 해당 인증이 AutoRAG에 연결되는 것은 아니다.

앞의 Lite 예제에서 init과 refresh를 마쳤다면 같은 설정과 인덱스를 full에서도 사용한다. OPENAI_API_KEY를 환경변수로 설정한 뒤, 현재 셸에서 사용할 모델을 지정하고 검색한다.

export AUTORAG_MODEL_PROVIDER=openai
export AUTORAG_MODEL_ID=gpt-5.6-terra
autorag health --json
autorag search "작년 제안서 사업비" --json

모델별 결과​

gpt-5.6-sol, terra, luna를 사고 수준 medium으로 맞추고 같은 질문을 돌렸다.

폴더질문terralunasol
블로그 23편133초, 정답31초, 정답57초, 정답
개인 노트 338편8평균 42초, 7/8 + 부분 1평균 51초, 8/8평균 56초, 7/8 + 부분 1
업무 문서 75개6평균 39초, 3/6 + 부분 3평균 36초, 1/6 + 부분 3평균 74초, 2/6 + 부분 2

Lite의 검색 시간은 약 1.3초였다. full은 검색 뒤 원문 확인과 답변 작성까지 수행하므로 수십 초가 걸렸고, 업무 문서에서 sol의 평균 응답 시간은 74초였다. 두 수치는 처리 범위가 다르다.

full 모드는 여러 글의 내용을 모아 답했다. 338편 실험에서 "미리 정리해 둔 지식 베이스 방식의 실패 모드"를 물었을 때 grep 에이전트는 글 하나를 찾아 답했고, full은 같은 주제를 다룬 글 두 편의 표를 모두 인용했다. "1인 개발자의 형상 관리 습관"을 물었을 때는 다른 글에 있던 "매일 커밋"을 보조 습관으로 덧붙이기까지 했다.

요약 과정에서 원문 표의 3.74배를 3.7배로 반올림한 사례가 있었다. 원문의 자릿수까지 보존해야 하는 용도라면 확인할 부분이지만, 이 차이만으로 오답이라고 보기는 어렵다. 원문에 적힌 구체적인 이유를 일반적인 설명으로 바꾼 문항도 있었다. luna는 예비 답에서 다른 벤치마크 수치를 영화 추천 수치로 잘못 짚었다가 최종 답에서 스스로 정정했다.

업무 문서에서는 full 모드의 정답률이 낮았다. 같은 6문항에 grep 에이전트와 Lite 에이전트는 전부 정답을 냈는데, 세 모델 모두 절반 이하였다. 회의록에 적힌 리스크 상태 흐름 "Open → 조치중 → Close"를 terra와 luna는 찾지 못했고, 대신 "신규 등록 → 추적·조치 → Close" 같은 표현을 만들어 냈다. 회의에서 바꾸기로 한 표현은 "확정"인데 luna와 sol은 "승인"이라고 답했다. RFP에는 종료 시점이 "계약 체결 후 ~ 2026년 12월 31일"이라고 적혀 있는데 terra와 sol은 "명시되지 않았다"고 했다. 학생 과제물의 데이터 출처(EPSIS)는 terra만 맞혔다. sol은 문서에 없는 KOSIS를 답했고, luna는 예비 답에서 맞게 EPSIS라고 했다가 최종 답에서 "근거를 확인할 수 없다"며 스스로 물러섰다.

긴 문서에서는 검색 누락과 답변 오류가 함께 관찰됐다. Lite 실험에서는 필요한 중간 청크가 검색 상위에 잘 나오지 않아도 Claude Code가 파싱된 문서를 다시 열어 답을 찾았다. full 모드에서는 원문에 없는 표현을 쓰거나 "확인되지 않는다"로 마무리한 경우가 있었다. 마크다운 폴더에서는 같은 문제가 두드러지지 않았다. 다만 비교에 사용한 모델과 원문 재탐색 방식도 달랐으므로, 이번 실험만으로 문서 길이, 검색 결과, 에이전트의 후속 탐색이 각각 얼마나 영향을 줬는지는 확인하지 못했다.

기본 설정에서는 web_search 도구가 켜져 있다. sol이 블로그 폴더를 검색하면서 폴더에 없는 "최신 MinSync 공식 문서"를 근거로 인용한 적이 있는데, 웹에서 가져온 내용이었다. 웹 검색을 끄는 방법은 뒤의 「문서 전송과 웹 검색」에 정리했다.

문서 찾기와 표 검색을 다시 해봤다​

어떤 작업에 쓸 만한지 좁혀 보기 위해 9월 23일에 v2.5.2 Lite로 추가 실험을 했다. AutoRAG 글을 제외한 공개 기술 블로그 23편에 질문 15개를 준비했다. 답이 있는 13개는 검색 전에 기대 문서를 지정했고, 나머지 2개는 코퍼스에 답이 없는 질문이었다. top-k를 1·3·5·10으로 바꿔 각각 실행했다. 여기서 측정한 것은 필요한 문서를 찾았는지이며, LLM의 최종 답변 정답률은 아니다.

용어는 잊었지만 내용을 기억할 때​

표현을 바꾼 한국어 질문 3개와 영어 질문 3개는 모두 기대 문서를 1위로 찾았다. “아직 채점하지 못한 항목을 실패한 항목과 섞으면 성적표가 왜 왜곡될까?”라는 질문으로는 평가의 0점과 측정 불가를 구분한 글을 찾았다. 영어로 늦게 돌아온 작업자가 다른 작업자의 결과를 덮지 못하게 하는 방법을 물었을 때도 한국어 lease fencing 글이 나왔다.

다만 첫 청크가 바로 답은 아니었다. 평가 관련 질문에서는 집계식을 설명한 부분이 아니라 글 후반의 주의점과 참고자료가 먼저 나왔다. 이 경우 AutoRAG로 문서 위치를 찾고 그 원문을 읽는 흐름이 유용했다. 특정 식별자를 이미 알고 있을 때는 달랐다. 0o444 검색은 그 문자열이 없는 Oracle 글을 1위로 반환했지만, 같은 토큰으로 rg를 실행하면 해당 문자열을 포함한 agentdir 글만 나왔다.

이 결과만으로 벡터 검색의 우위를 주장할 수는 없다. 같은 질문을 동일한 MinSync 인덱스의 BM25 단독 검색으로도 실행했더니, top-5에서 필요한 문서를 모두 찾은 질문 수는 AutoRAG와 똑같이 12/13개였다. 일부 질문의 순위는 달랐지만 이 작은 코퍼스에서는 검색 가능한 범위가 크게 벌어지지 않았다. BM25 비교는 MinSync를 직접 호출한 것이며, Claude Code의 전체 작업 성능을 다시 비교한 실험은 아니다.

개인 블로그 마크다운 307편에서도 별도로 준비한 7개 질문을 비교했다. “Which branches does the solo developer use for production and individual features?”로 물으면 AutoRAG는 「1인 개발자의 생산성 스택」을 1위로 찾았고, top-3에는 main과 feature/*의 용도를 적은 본문이 포함됐다. 같은 질문의 BM25 top-5에는 해당 글이 없었다. 반대 사례도 있었다. 지식베이스에 과거 설계 결정의 이유를 보존하는 법을 영어로 물었을 때 BM25는 기대한 「LLM Wiki와 ADR」을 1위로 찾았지만, AutoRAG는 top-5에서 놓치고 top-10에서야 찾았다. 영어 질문이라고 항상 벡터 결합이 유리하지는 않았다.

질문을 나누고 top-k를 바꿨을 때​

“원본 복제에 드는 저장 공간과 문서 수정 후 재임베딩 비용을 각각 줄이는 도구”를 한 질문으로 묶으면 top-10에서도 MinSync 글만 찾고 agentdir 글은 놓쳤다. 저장 공간과 재임베딩을 별도 질문으로 나누자 각각 top-3에서 필요한 글을 찾았다. 두 호출의 JSON 합계는 약 47KB로, 한 번에 top-10을 받은 약 69KB보다 작았다.

이 코퍼스에서는 top-k를 늘리는 것만으로 개선되지 않았다. 필요한 문서를 모두 찾은 질문은 top-3·5·10에서 똑같이 12/13개였지만, 전체 15질문의 반환량 중앙값은 약 22KB·35KB·70KB로 늘었다. 여러 목표를 섞은 질문이라면 후보 수를 늘리기 전에 목표별로 나눠볼 근거가 생겼다. 다만 분할 질문은 첫 결과를 본 뒤 만든 후속 시도이며, top-3이 항상 최선이라는 뜻은 아니다.

개인 블로그에서는 후보 수를 늘렸는데 근거가 빠지는 경우도 있었다. “1인 개발자 브랜치 전략”의 top-3에는 main과 feature/*를 설명한 청크가 있었지만, top-5에는 같은 글의 맺음말만 남고 설명 청크는 사라졌다. 두 설정을 한 번씩 다시 실행해도 같았다. 이 실행에서 top-5는 top-3에 두 청크를 덧붙인 결과가 아니었다. 필요한 근거를 찾았다면 후보 수를 늘리는 것보다 그 원문을 읽는 편이 확실했다.

파일 형식보다 변환된 본문이 중요했던 경우​

Downloads의 HWP·DOCX·PDF·XLSX·PPTX 10개도 별도 사본으로 검색했다. exact 중복 HWP 1개가 제외돼 9개가 인덱싱됐다. 학생 과제 HWP의 데이터 수집처를 물었을 때는 top-1 청크에 EPSIS가 들어 있었다. RFP의 종료일 질문은 top-1에서 올바른 문서를 찾았지만 날짜는 없었고, top-3까지 받아야 날짜를 확인할 수 있었다.

권한 확인용 XLSX에서는 다른 문제가 드러났다. 파싱은 성공했지만 한국어가 &#숫자; 형태의 문자 참조로 남아 있었다. 파일 자체가 검색돼도 필요한 설명을 읽기 어려웠다. 시트와 행을 보존하는 마크다운으로 별도 변환해 다시 인덱싱하자, 두 질문 모두 top-1 청크에서 접근권한을 구성하는 조건과 교집합 설명을 찾았다. 이 비교에서는 문자 디코딩과 행 구조가 함께 바뀌었으므로 각각의 효과를 분리하지는 못했다.

같은 처리를 한 WBS는 달랐다. 특정 작업의 진행 상태를 묻는 두 질문은 원본 XLSX와 별도 변환한 마크다운 모두 top-10에서 대상 문서를 찾지 못했다. 따라서 이번 결과는 HWP의 본문·표를 찾아 읽는 용도에는 도움이 됐지만, 복잡한 엑셀을 그대로 질의할 수 있다는 근거는 되지 못했다.

사용 전 확인할 설정​

문서 전송과 웹 검색​

외부 LLM으로 full 모드를 실행하면 답변에 사용하는 문서 내용이 해당 서비스로 전달된다. 실험에 쓴 중간보고서에는 담당자 연락처도 들어 있었다. Lite 자체의 검색은 로컬에서 처리하더라도, 그 결과를 외부 에이전트에 넘기면 해당 모델 입력에 포함된다.

web_search는 기본으로 켜져 있다. 로컬 문서만 근거로 답하게 하려면 config.json에 "webSearch": {"enabled": false}를 설정한다.

로컬 실행 로그의 쿼리 필드는 본문 대신 길이를 기록한다. 검색 메모리와 원문 근거도 별도로 저장하므로, 로컬에 보관되는 정보는 실행 로그보다 많다.

설치 환경과 인터페이스​

실험 환경의 MinSync 바이너리는 266MB였다. Java와 Rust 등의 설치 요건은 앞의 설치 절을 참고하면 된다. v2.5.2의 자동 배포 런타임 목록에는 Linux용 항목이 없었다. 이는 해당 자동 설치 경로의 제약이며, AutoRAG 전체가 Linux에서 실행되지 않는다는 뜻은 아니다.

웹 UI는 v2.5.2 설치 스킬 문서에서 개발 중으로 안내하고 있다. 이번 글의 설치와 검색은 CLI를 기준으로 했다.

정리​

문서 변환과 인덱스를 준비해 두고 기존 에이전트에서 검색하려면 Lite를, AutoRAG에서 출처가 붙은 답변까지 받으려면 full을 선택할 수 있다.

처음 비교한 마크다운 폴더에서는 Claude Code에 grep을 시키는 쪽이 더 빨랐고 정답률은 같았다. 후속 실험에서 AutoRAG Lite가 유용했던 작업은 용어를 잊은 문서의 위치를 찾거나 HWP의 본문과 표를 검색하는 일이었다. 복합 질문은 나눠 묻는 편이 나았고, XLSX는 변환 결과에 따라 검색 가능한 내용이 달랐다. full 모드가 긴 문서의 중간 내용을 놓치고 원문과 다른 표현으로 답한 경우도 있었으므로, 날짜나 수치, 상태 이름처럼 정확해야 하는 값은 출처를 열어 대조해야 한다.

References​

jikji: 에이전트의 파일 탐색 비용을 줄이는 로컬 인덱스

· 약 15분
김성연
AI Research Engineer, Braincrew

Jikji Architecture: 로컬 파일 탐색 맵의 준비·검색·응답 3단계 구조

에이전트에게 로컬 폴더를 붙이면 가장 먼저 하는 일은 탐색이다. ls로 트리를 훑고, cat으로 파일을 열고, grep으로 내용을 뒤진다. 원하는 파일 하나를 찾을 때까지 이 과정을 몇 번씩 반복한다. 잘 정리된 코드베이스라면 몇 번의 명령으로 끝나지만, 사람의 다운로드 폴더나 회사 공유 드라이브처럼 파일명이 제각각이고 비슷한 자료가 여러 곳에 흩어진 곳에서는 이 탐색만으로 컨텍스트 예산이 줄줄 샌다. 그리고 이 낭비는 매 요청마다, 매 파일마다 다시 발생한다. Hermes 같은 에이전트에 LLM을 붙여 실제로 파일을 찾게 해본 적이 있다면 알 것이다. 파일 하나 찾을 때마다 토큰이 녹는다.

문제는 두 방향으로 더 나빠진다. 첫째, 에이전트가 쓰는 검색 도구가 grep·find 같은 기본 CLI라서, HWP나 PDF처럼 바이너리로 감싸인 문서는 내용 탐색이 아예 안 되거나 극단적으로 비효율적이다. 둘째, 이제는 문서만이 아니라 이미지·영상도 내용 기반으로 찾아야 하는데, 그러자고 임베딩 모델을 얹고 벡터 DB로 RAG를 세우는 건 구축이 까다롭고 컴퓨팅 자원도 낭비다.

이전 글에서 다룬 agentdir는 이 문제의 절반, 즉 "에이전트가 보기 좋은 파일 구조를 만드는" 쪽을 담당했다. 하지만 agentdir는 README에서 파일 파싱, 인덱싱, 검색을 명시적 비범위로 선을 그었다. 정확히 그 빈칸을 채우는 도구가 같은 팀(NomaDamas)이 만든 jikji다.

  • GitHub: NomaDamas/jikji
  • 역할: 에이전트가 반복 탐색 없이 로컬 파일을 찾도록, 미리 검색 인덱스와 파일 맵을 만들어두는 도구
  • 핵심 명령: jikji prepare ROOT (인덱싱) → jikji find ROOT "clue" --json (한 번의 탐색 호출)

jikji의 자기 소개는 이렇다.

"Non-destructive local file maps and instant search indexes for AI agents." 파일 하나 찾을 때마다 38,650 토큰, 57초, 11.7회 LLM 호출을 쓰던 raw agent 탐색을, 파일당 447 토큰·2.1초·1회 호출로 줄이는 비파괴 로컬 탐색 스킬.

이 글은 위 아키텍처 그림(레포 스냅샷 9ecc942 기준)을 따라가며 jikji가 무엇을 하는지 보고, 실제로 설치해 돌려본 결과를 정리한다.

jikji가 하는 일​

jikji의 동작은 그림처럼 세 단계다.

  1. Prepare folder: jikji prepare ROOT. 폴더를 읽어 문서(PDF, HWP/HWPX, Office, 텍스트 등)를 파싱하고, 파일 카드·폴더 프로파일·에이전트 맵·각종 캐시를 만든다. 이미지·오디오·비디오는 opt-in 미디어 브리지로 처리한다.
  2. Build search map: 이 준비 과정이 만들어내는 산출물은 전부 .jikji/ 아래에 쌓인다. 파일명·경로 단서는 메타데이터 라우트로, 문서 내용 단서는 문서 텍스트 캐시로, 폴더 맥락은 에이전트 맵으로, 연관어·힌트는 위키 그래프로 각각 분리해서 인덱싱한다.
  3. Agent finds files: jikji find. 에이전트는 자연어 단서 하나를 던지고, 후보 슬레이트(candidate slate)·정답 경로(answer paths)·근거 팩(evidence pack)을 한 번에 받는다. 그리고 그 결과를 그대로 쓰거나(direct use), 실패 시 한 번 재시도하는 식으로 핸드오프한다.

핵심은 비파괴다. jikji는 사용자 파일을 옮기거나 이름을 바꾸거나 지우거나 재배치하지 않는다. 생성물은 전부 .jikji/ 디렉토리와 .jikji_agent_map.md로 격리된다. 그림 1단계에 non-destructive 뱃지가 붙은 이유가 이것이다.

비용 절감의 핵심​

jikji가 빠르고 저렴한 이유는 단순하다. 비싼 탐색 작업을 에이전트가 물어보기 전에 미리 끝내두기 때문이다.

에이전트가 실시간으로 파일을 찾는 방식은 매번 같은 비용을 다시 치른다. 문서를 열 때마다 그 자리에서 파싱하고, 경로를 모르니 트리를 헤매고, 쿼리를 추측으로 바꿔가며 여러 번 검색한다. 이 모든 단계가 LLM 호출과 토큰으로 환산된다.

jikji는 이 비용을 prepare 시점으로 앞당긴다.

  • 탐색 시점 파싱 루프 제거: 문서는 prepare 때 .jikji/doc_text/에 텍스트로 캐시된다. 검색 때 다시 열지 않는다.
  • 경로 방황 제거: 폴더 프로파일, 파일 카드, 중복 힌트, 라우트 행이 지저분한 트리를 에이전트가 읽을 수 있는 파일 맵으로 바꾼다.
  • 필드 분리 검색: 경로·파일명·폴더·확장자·본문·메타데이터를 따로 인덱싱해서, 명백한 경로 단서와 본문에만 있는 단서가 둘 다 잘 랭킹된다.
  • LLM 위키·지식 그래프: 소스마다 짧은 위키 요약 페이지를 만들어 두어, 에이전트가 큰 원본을 여는 대신 요약만 보고 판단할 수 있다. 노드(소스·폴더·용어·의도·중복)는 knowledge_graph.json과 graph_routes.jsonl로 미리 라우팅된다.

결과적으로 jikji find 한 번은, 여러 라우트에서 모은 후보를 하나의 슬레이트로 합쳐 에이전트에게 넘긴다. 에이전트는 이 top-k 슬레이트에 대해 한 번의 판단만 하면 되고, ls/find/grep/문서 열기/쿼리 추측에 채팅 턴을 쓰지 않는다.

결정론적 검색과 멀티미디어​

인트로에서 짚은 두 번째 고민, "시맨틱 검색을 하려면 결국 임베딩과 벡터 DB가 필요하지 않나"에 대한 jikji의 대답이 흥미롭다. jikji는 임베딩 모델도, 벡터 DB도, 클라우드 RAG 스택도 쓰지 않는다. README의 표현을 그대로 옮기면 이렇다.

This is RAG-style retrieval context, not a mandatory vector DB or cloud RAG stack. Jikji's default index is local and deterministic: no embeddings, cloud parser, or LLM call is required to prepare or search.

대신 prepare 단계에서 결정론적 시맨틱 용어(deterministic semantic terms)를 뽑아 필드로 인덱싱한다. 랭킹도 임베딩 유사도가 아니라 필드별로 가중치를 둔 BM25 역색인(SQLite 기반, FTS5도 벡터도 아니다)으로 이뤄진다. 앞서 직접 돌려본 find 응답에서 후보가 뽑힌 이유(why)에 intent-tag, contextual-anchor 같은 항목이 섞여 있던 것이 이 계층이다. 순수 문자열 매칭을 넘어선 의도·맥락 기반 랭킹을, LLM 추론이나 임베딩 없이 결정론적으로 만들어낸다는 것이 핵심 주장이다. 준비·검색 과정에서 LLM은 전혀 호출되지 않고, LLM은 오직 에이전트가 반환된 슬레이트에서 최종 선택을 할 때만 쓰인다.

멀티미디어도 같은 틀 위에 있다. PDF·HWP/HWPX·Office·텍스트·자막·HTML·JSON/YAML·아카이브는 기본으로 파싱·인덱싱되고, 이미지·오디오·비디오는 opt-in OCR/ASR로 내용까지 인덱싱할 수 있다. 별도 임베딩 학습 없이, 미디어 브리지가 콘텐츠를 텍스트로 뽑아 같은 검색 인덱스에 태우는 방식이다.

# 이미지·오디오·비디오 내용 인덱싱은 opt-in (CPU/RAM을 쓰므로 기본 비활성)
pip install "jikji[media]"
jikji prepare ROOT --enable-media-index --media-index-max-mb 25

이 절의 내용(결정론적 시맨틱 랭킹의 품질, 멀티미디어 OCR/ASR 검색)은 레포의 설계·주장이며, 아래 "직접 돌려본 결과"에서 내가 검증한 범위와는 구분해서 읽어달라. 나는 텍스트 문서 탐색까지만 직접 확인했다.

기본 사용법​

jikji가 노리는 사용 방식은 CLI를 직접 두드리는 게 아니라, 에이전트에게 스킬로 붙여두고 에이전트가 알아서 jikji find를 쓰게 하는 것이다. 저자가 공개한 안내처럼, Claude Code·Hermes 같은 에이전트에 이렇게 자연어로 시키면 된다.

GitHub 저장소 https://github.com/NomaDamas/jikji 에서 Jikji를 설치하고,
내 CLI 에이전트들이 jikji find를 바로 쓰도록 Jikji skill까지 연결해줘.

이 자연어 지시가 실제로 매핑되는 명령은 스킬 설치 계열이다. agent-skill-install은 공용 에이전트들에 jikji 스킬 지시문을 설치하고, 필요하면 특정 에이전트로 내보낼 수도 있다.

jikji agent-skill-install --agent all --json   # 공용 에이전트에 스킬 설치
jikji hermes-skill-install --json # Hermes 전용
jikji skill-export --dest /path/to/agent/skills/jikji/SKILL.md --json

이렇게 붙여두면 에이전트는 파일을 찾을 때 grep·find로 헤매는 대신 jikji find를 호출하고, 앞서 본 근거 팩과 핸드오프 정책을 그대로 받아 판단한다. (이 스킬 설치 명령들은 사용자의 실제 에이전트 설정 디렉토리에 파일을 쓰므로, 이 글에서는 동작만 소개하고 직접 실행하지는 않았다.)

직접 돌려본 결과​

이 도구가 실제로 약속을 지키는지 보려고, 어지러운 코퍼스를 하나 만들어 macOS에서 돌려봤다. 현재 레포는 Rust 포팅 중인 모노레포라 crates/의 Rust CLI가 정식 구현이지만, python/jikji/가 동일 동작의 레퍼런스 구현으로 남아 있다. 이 환경엔 Rust 툴체인이 없어 Python 레퍼런스 구현(0.1.0)을 소스에서 설치해 측정했다. (주의: PyPI의 jikji 패키지는 전혀 다른 Flask 정적 사이트 생성기다. 이 도구가 아니다. 반드시 레포에서 설치해야 한다.)

코퍼스는 다운로드 폴더처럼 흩어진 파일 6개로 합성했다. 회의록_최종_진짜최종.md, report_v2_draft.txt, 고객사자료/제안서.md, random_notes.txt, temp/build_log.txt, 그리고 30MB짜리 더미 dataset.csv를 여러 하위 폴더에 뿌렸다.

1. prepare는 비파괴적이고 빠르다​

$ jikji prepare ./corpus
Jikji prepared: .../corpus
- files=6 folders=4 deleted=0
- docs parsed/reused/failed=0/0/0

파일 6개 기준 약 0.17초에 끝났고, deleted=0이며 내가 만든 원본 6개는 그대로였다. 그림 2단계에 나온 산출물이 실제로 .jikji/ 아래에 생성됐다. search_index.sqlite, doc_text/, file_cards.jsonl, folder_profile.jsonl, knowledge_graph.json, graph_routes.jsonl, wiki/ 가 모두 확인됐다.

한 가지 그림에 안 나온 사실도 있다. prepare는 .jikji/ 외에 코퍼스 루트에 에이전트 포인터 파일도 쓴다. 내 경우 .jikji_agent_map.md 말고도 AGENTS.md, CLAUDE.md, .cursorrules가 루트에 새로 생성됐다. 원본을 수정·삭제하진 않지만, "생성물은 .jikji/에만"이라는 요약보다는 루트에 몇 개 파일이 더 놓인다는 점을 알고 쓰는 게 좋다.

2. find는 근거가 붙은 후보 슬레이트를 돌려준다​

"분기 보고서 핵심 지표 토큰"이라는 자연어 단서로 --json을 붙여 호출했더니, 정답인 downloads/report_v2_draft.txt를 1순위로 랭킹하면서 이런 근거 팩을 돌려줬다.

{
"path": "downloads/report_v2_draft.txt",
"why": ["multi-token-overlap", "doc-type-match", "intent-tag",
"contextual-anchor", "body-coverage"],
"matched_terms": ["분기", "보고서", "핵심", "지표", "토큰"],
"evidence": ["핵심 지표: Hit@1 정확도, LLM 호출 수, 총 토큰 사용량"],
"next_read": { "kind": "original", "path": "downloads/report_v2_draft.txt" }
}

주목할 부분은 이 페이로드가 단순히 경로 목록이 아니라는 점이다. 왜 이 파일이 뽑혔는지(why), 어떤 용어가 맞았는지(matched_terms), 본문 어디에 근거가 있는지(evidence), 다음에 뭘 읽어야 하는지(next_read)까지 들어 있다. 에이전트는 이걸 보고 원본을 열지 않고도 판단할 수 있다.

3. 페이로드가 추가 탐색을 막는다​

find --json의 전체 응답에서 가장 인상적이었던 건 핸드오프 정책 필드였다. 응답에는 이런 것들이 함께 들어온다.

"handoff_action": "direct_use",
"answerability": "answerable_from_payload",
"tool_call_policy": {
"stop_after_find": true,
"forbidden_tools": ["read_file", "search", "grep", "rg",
"find", "fd", "ls", "cat", "tree", "glob"],
"rerank_locked": true
},
"allowed_agent_tool_calls": 0,
"allowed_llm_calls": 0

즉 jikji는 "여기 답이 있으니, grep·ls·cat 같은 도구를 더 쓰지 말고 이 페이로드로 끝내라"고 에이전트에게 명시적으로 지시한다. 토큰 절감의 메커니즘이 여기 있다. 절감은 인덱스가 빨라서가 아니라, 에이전트의 추가 탐색 루프 자체를 막아서 나온다. 이 전체 응답은 약 5.3KB(대략 1,300 토큰 상당)였다. 파일 하나 찾자고 트리를 반복해서 훑는 것과 비교하면 작다.

4. 코퍼스를 키우면 차이가 벌어진다​

위 6개짜리 코퍼스는 너무 작아서 raw와 jikji가 둘 다 쉽게 맞혔다(뒤의 벤치마크 절 참고). 그래서 지저분한 작업 폴더를 좀 더 크게 흉내 낸 합성 코퍼스(파일 530개, 폴더 42개)를 만들고, 정답을 미리 아는 자연어 질의 10개로 같은 결정론적 비교(jikji 인덱스를 쓰지 않는 raw 베이스라인 vs jikji)를 다시 돌렸다. 모델 없이 순수 검색 품질만 본 결과다.

지표raw (jikji 인덱스·파서 미사용)jikji
Hit@10.601.00
Hit@100.801.00
MRR0.6671.000

같은 폴더, 같은 질의에서 jikji가 정답 파일을 상위로 올리는 능력이 분명히 좋았다. 다만 이 표는 검색 품질의 재현이지 비용·시간의 근거는 아니다. 소형 코퍼스에서는 CLI 실행이나 함수 호출 오버헤드가 시간 지표를 왜곡하므로 시간은 비교하지 않았고, 단일 합성 코퍼스에 소표본(질의 10개)이라 절대값보다 방향으로 읽어야 한다. 또 raw 쪽이 파서 캐시를 쓰지 않아 바이너리 문서 본문을 못 읽는 비대칭이 있어, 이 차이의 상당 부분은 "미리 파싱해 둔 인덱스가 있느냐"에서 온다. 바꿔 말하면 그 인덱스가 바로 jikji가 파는 것이기도 하다.

재현 메모: macOS에서 jikji Python 레퍼런스(0.1.0)를 레포 소스(python/jikji)에서 설치해 측정했다. 1)~3)의 근거 팩 데모는 흩어진 파일 6개(더미 30MB CSV 포함) 코퍼스에서, 4)의 검색 품질 재현은 파일 530개 코퍼스에서 각각 돌렸다. 위 수치들은 단일 데모 기준이며, 절대값보다 동작의 성격에 주목해 달라. 1)~3)의 wc -c 기반 바이트→토큰 환산(≈4바이트/토큰)은 정밀 토크나이저 측정이 아니라 대략적 크기 감이다.

벤치마크 결과: 모델링과 실측​

그림 오른쪽 아래의 "38,650 → 447 tokens", "56.7s → 2.1s"는 레포가 제시하는 벤치마크 수치다. 이 부분은 내가 직접 재현하지 못했다. 이 숫자는 실제 LLM 에이전트(Hermes)를 붙여 raw 탐색과 jikji 탐색을 비교하는 벤치마크에서 나오는데, 그러려면 LLM 에이전트 루프가 필요하다. 이 환경에서 돌린 내장 bench-run은 LLM이 없는 결정론적 렉시컬 베이스라인이라, 6개짜리 작은 코퍼스에서는 raw와 jikji가 둘 다 Hit@1=1.0으로 나와 토큰/호출 차이를 만들어내지 못했다. 즉 "왜 답이 뽑히는가"는 위에서 직접 확인했지만, "얼마나 싸지는가"의 절대 수치는 레포의 주장으로 받아들여야 한다.

레포가 밝힌 벤치마크(HippoCamp Fullset, 551건, 동일 Hermes 태스크 범위)는 다음과 같다. 아래 표와 배수는 모두 레포의 자체 측정치다.

지표raw HermesJikji find개선
케이스 수551551-
Hit@10.66970.7949상승
Hit@100.77860.7949상승
LLM 호출6,42055111.65× 감소
총 토큰21,296,278246,31686.46× 감소
벽시계 시간31,231.9s1,164.2s26.83× 감소

케이스당 평균으로 보면 raw는 11.7회 호출·38,650 토큰·56.7초를 썼고, jikji find는 1회 호출·447 토큰·2.1초를 썼다(그림의 그 숫자다). 정확도가 떨어지는 대가로 비용을 줄인 게 아니라, Hit@1이 0.67 → 0.79로 오히려 올랐다는 점이 이 벤치마크의 핵심 주장이다.

다만 이 표를 인용할 때 한 가지는 갈라 읽는 게 정확하다. 위 "Jikji find" 행은 레포가 실제로 측정한 라이브 실행이 아니라, 반환된 후보 슬레이트를 두고 LLM이 한 번에 정답을 고른다고 가정한 모델링 값이다. 레포의 docs/jikji-value-report.json에서 확인했다. 이 행은 정답 파일이 상위 후보 안에 있으면 judge가 항상 맞힌다고 보기 때문에 Hit@1과 Hit@10이 0.7949로 같고, 토큰은 하드코딩 상수와 길이 휴리스틱으로 추정하며(리포트 자신이 "실제 프로바이더 사용량이 아니라 추정치"라고 라벨링한다), 시간은 answer-pack 검색 시간(실측)에 호출당 1.5초를 가정해 더한 값이다.

같은 리포트에서 실제로 LLM 에이전트를 붙여 측정한 라이브 실행(jikji-discover)은 더 겸손하지만 그래도 분명한 승리다. Hit@1 0.688(raw 0.670), Hit@10 0.800(raw 0.779)에, 토큰은 약 2.8배(2,130만 → 760만), LLM 호출은 약 2.8배, 벽시계 시간은 약 2배(31,232s → 15,603s) 줄었다. 반대로 LLM을 전혀 쓰지 않는 순수 검색 모드(jikji-answer-pack)의 Hit@1은 0.514로 raw(0.670)보다 낮다. 정확도 상승은 후보 slate를 두고 한 번 판단하는 LLM 호출이 있어야 생긴다는 뜻이다. 정리하면 헤드라인의 "86배·27배·Hit@1 0.79"는 이 도구를 가장 이상적으로 썼을 때의 천장으로 읽고, 붙이면 바로 나오는 실측 절감은 토큰·시간 약 2~3배에 정확도 동률 수준으로 읽는 게 정확하다. 그리고 이 수치들은 NomaDamas가 단일 모델로 한 번 돌린 self-run이며 제3자 독립 재현은 아직 없다.

시리즈와 AutoRAG의 관계​

같은 팀의 두 도구를 나란히 놓으면 분업이 선명하다.

  • agentdir (이전 글): 원본을 건드리지 않고 에이전트가 보기 좋은 파일 레이아웃(구조)을 만든다. 파싱·인덱싱·검색은 비범위.
  • jikji: 원본을 건드리지 않고 에이전트가 파일을 찾을 검색 인덱스·맵(내용)을 만든다. 구조 재배치는 하지 않는다.
  • MinSync (직전 글): 임베딩 벡터 인덱스를 운영 중에 신선하게 유지한다. 바뀐 청크만 다시 임베딩한다.

셋 다 "원본 비파괴 + 에이전트용 뷰를 따로 생성"이라는 같은 철학 위에 있지만 맡는 층이 다르다. agentdir는 배치, jikji는 발견, MinSync는 의미 인덱스의 신선도다. 특히 jikji와 MinSync는 이름이 비슷해 헷갈리기 쉬운데 겨냥하는 곳이 다르다. jikji는 임베딩 없이 결정론적으로 "어느 파일인지"를 싸게 좁히고, MinSync는 임베딩 벡터 인덱스를 최신으로 유지한다. 둘은 경쟁이 아니라 이어 붙는 관계에 가깝다. jikji가 지저분한 폴더를 몇 개 후보 파일로 좁히면, MinSync로 신선하게 유지한 벡터 RAG가 바로 그 몇 개 안에서 의미 기반 심층 검색을 하는 식이다. RAG/에이전트 파이프라인 관점에서 보면 jikji는 인제스트 이전의 탐색·후보 선정 레이어다. 에이전트가 코퍼스에서 관련 문서를 찾는 단계를, 매번 LLM으로 헤매는 대신 결정론적 로컬 인덱스로 대체한다. prepare 산출물에 autorag_manifest.json(AutoRAG 연동 계약), chunk_map.jsonl 같은 파일이 들어 있는 이유가 여기 있다.

이 연결은 2026년 7월에 나온 AutoRAG 2.0에서 실제로 이어졌다. 새 AutoRAG는 RAG AutoML 도구가 아니라 로컬 문서를 찾아 읽고 정리하는 에이전트인데, autorag refresh를 돌리면 파싱과 MinSync 동기화에 이어 jikji prepare가 같이 돌면서 원본 폴더 안에 .jikji/를 만든다. full 모드에서는 에이전트에게 jikji_find라는 도구로 노출돼, 검색을 시작하기 전에 후보 파일을 좁히는 데 쓰인다. jikji 바이너리가 없으면 AutoRAG가 첫 실행에서 cargo install jikji-cli로 직접 빌드하므로 Rust 툴체인이 필요하다. 실제로 돌려본 기록은 AutoRAG Agent 글에 있다.

jikji README는 여전히 자신을 "AI 에이전트를 위한 로컬 파일 탐색 스킬"로 소개하고, AutoRAG 없이 단독으로도 쓸 수 있다. 이 글의 벤치마크와 사용법은 그 단독 사용 기준이다.

한계와 주의점​

직접 써보며, 혹은 문서를 읽으며 확인한 지점들이다.

  • 정식 구현은 Rust CLI, 내가 돌린 건 Python 레퍼런스다. 두 구현은 parity 테스트로 맞춰지지만, 세부 동작이나 성능은 다를 수 있다. Rust CLI(cargo)로 쓰면 이 글의 설치 과정과 달라진다.
  • 벤치마크 수치는 재현하지 못했다. 앞서 밝혔듯 토큰/호출 절감의 절대값은 LLM 에이전트 벤치마크에서 나오며, 이 글에서는 레포 주장으로 인용했다.
  • prepare는 루트에도 파일을 쓴다. .jikji/뿐 아니라 AGENTS.md, CLAUDE.md, .cursorrules, .jikji_agent_map.md가 루트에 생성됐다. 원본은 안 건드리지만, 버전 관리 중인 폴더라면 .gitignore 처리를 고려해야 한다.
  • 인덱스는 스냅샷이다. 원본이 바뀌면 refresh로 다시 준비해야 하고, jikji는 인덱스가 낡았는지(freshness) 알려주되 검색 중에 몰래 재인덱싱하지는 않는다.
  • jikji는 두뇌가 아니다. 무엇을 찾을지 판단하는 건 여전히 에이전트다. jikji는 후보 슬레이트와 근거를 줄 뿐이고, 최종 선택은 에이전트의 몫이다.

정리​

에이전트에게 로컬 폴더를 주면 매번 탐색부터 다시 시작한다. jikji는 그 비싼 탐색을 prepare 한 번으로 앞당겨 갚아두고, 이후에는 find 한 번으로 근거가 붙은 후보 슬레이트를 돌려준다.

직접 돌려 확인한 건 세 가지다. prepare는 원본을 건드리지 않고 그림 속 산출물을 실제로 만들어냈고(단, 루트에 포인터 파일 몇 개를 추가한다), find --json은 경로가 아니라 왜·무엇이·어디서 맞았는지가 담긴 근거 팩을 돌려줬으며, 그 페이로드는 에이전트에게 grep·ls·cat을 더 쓰지 말라고 명시적으로 지시했다. 토큰 절감의 정체는 빠른 인덱스가 아니라 바로 이 "그만 찾아라" 신호였다. 반면 "38,650 → 447 토큰" 같은 절대 수치는 이 환경에서 재현하지 못했고, 레포의 자체 벤치마크 주장으로 남겨둔다.

agentdir가 에이전트에게 정돈된 파일 구조를 줬다면, jikji는 그 위에서 파일을 찾는 비용을 미리 갚아주는 인덱스 레이어다. 둘 다 원본은 그대로 둔 채로.

References​