1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI エージェント用の検索エンジン「XERJ」を Mac で動かしてみた

1
Posted at

AI エージェント用の検索エンジン「XERJ」を Mac で動かしてみた

はじめに

Claude Code や Codex のようなコーディングエージェント(AI が自分でコマンドを打ちながら開発を進める道具)に「この処理はどこに書いてある?」と聞くと、多くの場合 grep や ripgrep(rg)でリポジトリ全体を文字列検索し、ヒットしたファイルを開いて読みます。小さなリポジトリならこれで十分ですが、大きくなるほどヒットが増え、読むファイルも増え、そのぶんトークン(AI が読み書きする文字量の単位で、料金と速度に直結します)を消費します。

この「探す」部分を、あらかじめ作った索引に置き換えようというのが XERJ です。2026 年 10 月 25 日(日本時間)に v1.0.0-rc.99 が公開されました。コード・ドキュメント・ログ・PDF などをまとめて索引し、エージェントには「必要な箇所の数行と行番号」だけを返します。開発元は「grep で探すエージェントと同じ正解数のまま、出力トークンが約 2.7 分の 1 になった」と公表しています。

本当にそうなるのかを確かめるため、筆者の Mac に XERJ を入れ、XERJ 自身のリポジトリ(git で管理されている 3,309 ファイル)を索引して、検索の当たり方と、Codex に解かせたときのトークン量を測りました。この記事で分かることは次の 4 つです。

  • XERJ で何ができ、grep やベクトル DB を使う従来のやり方と何が違うのか
  • 公式の「2.7 分の 1」が、どんな条件で測られた数字なのか
  • Mac での導入手順と、Claude Code・Codex への接続方法(実際に通したもの)
  • 導入前に知っておきたい注意点(とくに、利用報告の自動送信)

想定読者は、Claude Code や Codex を日常的に使っていて、大きなリポジトリでの探索コストが気になっている方です。Elasticsearch やベクトル検索の知識はなくても読めるように、用語には短い説明を添えています。

grep はヒットした行の場所だけを返し、確かめるためにファイルを丸ごと読む必要がある。XERJ は索引から定義の数行と行番号を返すので、読む量が小さくて済む

grep の答えは「何行目にその文字列があるか」まで。XERJ はコードを関数単位で索引しているので、名前で引けば定義そのものが返ってきます(コマンドと件数は筆者の実行、作業メモリの量はイメージ)。

先に結論

自分で測ってみた結果を先にまとめます。数字の詳しい条件は後半の「実測」で説明します。

確かめたこと 結果
関数名で定義を引く(xerj def) 8 問中 7 問で正解のファイルが 1 位。返る量は 600〜1,300 字
短いキーワードで探す(xerj search) 正解が上位 5 件に入ったのは 8 問中 3 問
英語の文で探す(xerj search) 上位 5 件に入ったのは 8 問中 1 問
Codex(GPT-6 Astra)に 8 問 × 3 回解かせる 3 条件とも 24 回すべて正解。入力トークンは XERJ ありの方が多く、grep だけの 1.53 倍(CLI 経由)・1.49 倍(MCP 経由)
Claude Code 2.1.296 に MCP で接続 接続でき、22 個のツールが一覧に出た(モデルからの呼び出しは未確認)

名前やキーワードが分かっているときの XERJ は速くて正確です。一方で、「〜する処理はどこ?」という文での検索は既定の設定では外れやすく、3,000 ファイル規模のリポジトリでは Codex のトークンも減りませんでした。公式も書いているとおり、XERJ が効くのは「AI が知らないコード」を使って書かせる場面で、中規模のリポジトリで場所を探すだけなら grep で足りていました。

この記事の確認範囲

  • 確認日:2026 年 10 月 26 日
  • XERJ:v1.0.0-rc.99(aarch64-apple-darwin 版、SHA-256 を公式の値と照合済み)
  • 環境:Apple M5 Max・メモリ 128GB・macOS 26.6.2
  • エージェント:Codex CLI 0.159.2(gpt-6-astra、推論の深さ high)、Claude Code 2.1.296(接続の確認のみ)
  • 公式の数字は README・ケーススタディ・リリースノートから引用し、筆者は再現していません。筆者が測った数字は「筆者の計測」と明記しています

XERJ とは何か

XERJ は、Rust で書かれたローカル(自分のマシンの中で動く)検索エンジンです。ライセンスは Apache-2.0 で、2026 年 6 月 30 日に公開されたリポジトリが、10 月 26 日時点で GitHub のスター 15,586 まで伸びています。ダウンロードするのは xerj という 1 つのファイルだけで、Java(JVM)も外部のベクトルデータベースも要りません。

その 1 つのファイルが、次の 3 つの役割を兼ねています。

フォルダを xerj autoindex で索引し、XERJ ノードが検索を受け持ち、CLI と MCP サーバーを通して AI エージェントが使う全体像

索引を作る xerj autoindex、索引を持って検索に答えるノード(サーバー)、エージェントの窓口になる CLI と MCP サーバー。すべて同じバイナリで、既定では 127.0.0.1(自分の Mac)だけで待ち受けます。

主な機能を表にまとめます。

機能 内容
自動の索引づけ xerj autoindex <フォルダ> で、中身から種類を判定して索引を作る。コードは tree-sitter(多くの言語の構文を解析するライブラリ)で関数・型の名前と行番号つきで登録
対応する形式 ソースコード、Markdown、HTML、JSON・JSONL・CSV・XML・YAML、SQLite、PDF、DOCX、PPTX、XLSX、EPUB、Jupyter、メール(eml・mbox)、ログなど
検索 BM25(単語の出現頻度で順位を付ける定番の全文検索)、ベクトル検索(kNN)、両者を混ぜるハイブリッド検索
Elasticsearch 互換 Elasticsearch と同じ API・クエリの書き方。既存のクライアントや Kibana をそのまま向けられる(公式の互換テスト 1,454 件中 1,444 件が成功、失敗 0 件、10 件はスキップ)
MCP サーバー xerj mcp でエージェントから使えるツールを 22 個提供(xerj_search・xerj_def・xerj_hybrid_search・xerj_memory_store など)
エージェントの記憶 /_memory/{名前} に覚えたことを保存し、次のセッションで意味の近さで呼び出す
その他 TypeSafe AI の Jev による並べ替え(任意・外部送信あり)、ラベル付きデータの多数決で判定する /_decide など

MCP(Model Context Protocol)は、AI エージェントに外部の道具をつなぐための共通の約束事です。MCP サーバーとして登録すると、エージェントは xerj_search などを自分の道具として呼べるようになります。

従来のやり方と何が違うのか

「コードを検索する」こと自体は昔からできました。XERJ が新しいのは、個々の技術ではなく組み合わせ方です。代表的な 4 つのやり方と比べてみます。

やり方 準備 エージェントに返るもの 弱いところ
grep / rg 不要 ヒットした行(ファイル名と行番号) 本命かどうかはファイルを開かないと分からない。大きいリポジトリほど出力も読む量も増える
Elasticsearch + ベクトル DB JVM・DB・埋め込みの処理を自分で組む 設計しだい 1 人の開発機でリポジトリ 1 つを探すには大がかり
既存のコード検索 MCP 製品による。埋め込みモデルやベクトル DB を別に立てる前提のものもある 意味の近いコード片 対象がコードだけ、検索方式が 1 種類だけ、という製品もある
モデルの記憶に頼る 不要 なし(覚えていることを書く) 社内コードや新しい API では、存在しない関数を書いてやり直しが続く
XERJ バイナリ 1 つ・設定ほぼなし 定義や該当箇所の数行と行番号 既定の検索は単語の一致が頼り(後述)

BM25 もベクトル検索も tree-sitter も MCP も、どれも以前からある技術です。XERJ はそれを「開発機 1 台・バイナリ 1 つ・設定ほぼゼロ」で AI エージェントに渡せる形にまとめました。モデルを変えずに、エージェントの周り(ハーネス)を変えてトークンを減らす、という発想です。逆に言えば、すでに Elasticsearch とベクトル DB を運用している組織にとっては、目新しさは「エージェント向けに絞った手軽さ」にあります。

v1.0.0-rc.99 で変わったこと

rc.99 は 2026 年 10 月 24 日 23 時 11 分(UTC)に公開された 1.0 の候補版(RC)です。リリースノートに並ぶ約 120 件の変更から、エージェントでの使い勝手に関わるものを抜き出します。

変更 中身
MCP の xerj_search に prefix 引数(PR 2137・2235) autoindex --prefix で付けた名前で、検索対象のリポジトリを絞れる
MCP に xerj_def(PR 2166) 手元の索引から関数・型の定義を引く。CLI の xerj def と同じ順位付け
xerj_batch_search(PR 2333) 複数の質問をまとめて投げ、順番どおりに結果を返す
検索結果に一致した行を表示(PR 2269) 返した断片の中で、どの行が当たったかを match_line で示す
macOS の未ダウンロードのファイルを飛ばす(PR 2223) iCloud などの「中身がまだ手元にない」ファイルで索引が止まらないように
索引処理の安定化(多数) 巨大な JSON の取り込み、メモリ不足での停止、更新の待ち状態などを修正

なお GitHub のリリース画面では、rc.99 は「プレリリース」の印が付いていない通常のリリースとして扱われています。ただし名前のとおり 1.0 の候補版で、CHANGELOG によると 10 月だけで rc.81(10 月 3 日)から rc.99 まで 19 回も版が出ています。業務で使うなら、安定版を待つか、版を固定して試すのが安全です。

公式の「2.7 分の 1」を正しく読む

XERJ の README の冒頭には、「grep で探すエージェントより出力トークンが 2.7 分の 1、正解数は同じ 16/16」という数字が載っています。この数字は、開発元のケーススタディ「reference coding」から来ています。条件を図にまとめました。

公式ケーススタディの数字。モデルが知らないライブラリの課題では XERJ が grep より出力トークン約 2.7 分の 1、有名 OSS では検索しないのが最も安い

左:実験用に書き下ろしたライブラリを使う課題。右:モデルが中身を覚えている有名 OSS(valkey・memcached)の課題。数字はすべて公式ケーススタディより。

読み取るときのポイントは 3 つです。

  1. 題材は「この実験のために書き下ろした小さなライブラリ」です。 研究全体では Rust を中心に 13 個が作られ、「2.7 分の 1」はそのうち Python・JavaScript・C・Java の 8 課題の数字です。モデルが学習で見たことのない API を使わせるため、わざと作られています。記憶だけで解かせると、存在しない関数名を書いてはコンパイルエラーになり、やり直しのたびに出力トークンが積み上がります(26 万トークン)。XERJ はここで本物の定義を渡すので、やり直しが消えます。
  2. 「reference coding」は、自分のリポジトリを探す話とは少し違います。 課題に近い OSS を git clone して索引し、「他のプロジェクトはどう解いたか」を引いてから書く、という使い方です。開発元自身も、大きな社内コードでの効果は「まだ測っていない」と書いています。
  3. モデルが知っているコードでは逆転します。 valkey と memcached を使った対照実験では、記憶だけ(1.49 ドル)がいちばん安く、XERJ(4.40 ドル)は 6 回中 1 回失敗しました。開発元は「効くかどうかは、モデルがそのコードを覚えているかで決まる」とはっきり書いています。

つまり公式の数字は、「AI が知らない API を正しく使わせる」場面の効果です。後半の筆者の計測は、これとは別の「自分のリポジトリの中から場所を探す」場面を測っているので、結果が違っても矛盾ではありません。

Mac で動かす手順

ここからは筆者の Mac(Apple Silicon)で実際に通した手順です。全体の流れは次の 5 ステップです。

ダウンロードと照合、ノードの起動、フォルダの索引、手で引いてみる、エージェントに MCP で繋ぐ、の 5 ステップと各コマンド

左の各ステップに対応するコマンドが右に出ます。いちばんつまずきやすいのは --prefix の付け忘れと、MCP 設定のパスです。

ステップ 1:ダウンロードして照合する

公式のワンライナーは curl -fsSL https://xerj.org/get | sh です。中身を読むと「OS と CPU を判定 → GitHub のリリースから取得 → SHA-256 を照合 → ~/.local/bin に置く」という素直なスクリプトでした。ただ、インターネットから取ったスクリプトをそのまま sh に流すのが気になる場合は、同じことを手で行えます。

# 作業用のフォルダで実行する
B=https://github.com/xerj-org/xerj/releases/download
V=v1.0.0-rc.99
F=xerj-1.0.0-rc.99-aarch64-apple-darwin.tar.gz   # Intel Mac は x86_64-apple-darwin

curl -fsSLO "$B/$V/$F"
curl -fsSLO "$B/$V/$F.sha256"

# 2 行の英数字が一致すれば、公式が配布したファイルそのもの
cat "$F.sha256"
shasum -a 256 "$F"

# 展開して ~/.local/bin に置く(公式のインストーラーと同じ場所)
tar -xzf "$F"
mkdir -p ~/.local/bin
cp xerj-1.0.0-rc.99-aarch64-apple-darwin/xerj ~/.local/bin/
export PATH="$HOME/.local/bin:$PATH"   # PATH に入っていなければ ~/.zshrc にも書く
xerj --version   # xerj v1.0.0-rc.99

筆者の環境では、ダウンロードは約 29MB、展開後のバイナリは約 127MB でした。

ステップ 2:ノード(検索サーバー)を起動する

# 索引するフォルダの「外」にデータを置く(中に置くと自分自身を索引してしまう)
xerj --insecure --data-dir ~/xerj-data

--insecure は、通信の暗号化(TLS)と API キーの認証を両方とも切る指定です。届く人は全員が管理者になるので、1 人で使う開発機に限ります。そのかわり、既定では 127.0.0.1(自分の Mac)だけで待ち受け、それ以外のアドレスで暗号化なしに公開しようとすると起動を拒否します。筆者の環境でも、待ち受けは 127.0.0.1 の 9200・8080・8081 番だけでした。

# 別のターミナルで確認する
curl -s localhost:9200
# {"name":"local", ... "tagline":"You Know, for Search"}   ← Elasticsearch と同じ形の返事

lsof -nP -iTCP -sTCP:LISTEN | grep xerj
# xerj ... TCP 127.0.0.1:9200 (LISTEN)

共有のサーバーで使う場合は --insecure を付けずに起動します。初回に <データの場所>/admin.key が作られるので、クライアント側でその鍵を渡します。

xerj --data-dir ~/xerj-data
export XERJ_API_KEY="$(cat ~/xerj-data/admin.key)"   # autoindex などはこの変数を読む

ステップ 3:フォルダを索引する

# 1 つのリポジトリに 1 つの名前(prefix)を付ける
xerj autoindex ~/dev/myapp --prefix myapp

--prefix を付けないと、すべて既定の ax という名前で登録されます。リポジトリを複数入れるときは必ず付けてください。同じ prefix で別のフォルダを索引しようとすると、上書きを防ぐために拒否されます。

索引が終わると、何が入ったかを確かめられます。

xerj autoindex map --prefix myapp                    # 見つかったデータの種類と件数
curl -s 'localhost:9200/_cat/indices/myapp-*?v'      # Elasticsearch と同じ書き方で一覧

ステップ 4:手で引いてみる

エージェントに繋ぐ前に、自分で引いて感触をつかんでおくのがおすすめです。

# 名前が分かっているなら def(定義へジャンプ)がいちばん確実
xerj def --prefix myapp loadConfig

# 名前が分からないときは search。長い文章より、コード中の単語を 2〜3 個
xerj search --prefix myapp "retry backoff"

# 返す件数や 1 件の長さを変える
xerj search --prefix myapp -k 10 --full 1200 "retry backoff"

筆者が XERJ のリポジトリで実行した例です。xerj def は 0.02 秒で、定義の場所とシグネチャ(関数の名前と引数・戻り値の型)を返しました。

$ xerj def --prefix xerj tiered_cap_mb

─── engine/crates/xerj-engine/src/governor.rs:1075  [function]
    fn tiered_cap_mb(effective_bytes: u64) -> u64

ステップ 5:エージェントに MCP で繋ぐ

最後に、エージェントの道具として登録します。どのエージェントでも中身は「xerj の絶対パス」と「引数 mcp」の 2 つです。デスクトップのアプリから起動されるエージェントはシェルの PATH を引き継がないため、xerj だけではなく、command -v xerj で調べた絶対パスを書きます。

あわせて、環境変数 XERJ_DISABLE_FEEDBACK=true を入れておきます。理由は後の「注意点」で説明しますが、これがないとエージェントが利用報告を外部へ自動で送ろうとするおそれがあります。

Claude Code(プロジェクト直下の .mcp.json に書く):

# プロジェクトのフォルダで実行する。-e は名前(xerj)の後ろに、コマンドは -- の後ろに書く
claude mcp add xerj --scope project \
  -e XERJ_URL=http://localhost:9200 \
  -e XERJ_DISABLE_FEEDBACK=true \
  -- "$(command -v xerj)" mcp

-e を名前より前に書くと、名前まで環境変数として読まれて「Invalid environment variable format」になりました。プロジェクトの .mcp.json に書いたサーバーは、初回に Claude Code の画面で使用を承認するまで「Pending approval(承認待ち)」のままです。

Codex(~/.codex/config.toml に書く):

codex mcp add xerj \
  --env XERJ_URL=http://localhost:9200 \
  --env XERJ_DISABLE_FEEDBACK=true \
  -- "$(command -v xerj)" mcp

実行すると、config.toml に次の表が追加されます。

[mcp_servers.xerj]
command = "/Users/you/.local/bin/xerj"
args = ["mcp"]

[mcp_servers.xerj.env]
XERJ_DISABLE_FEEDBACK = "true"
XERJ_URL = "http://localhost:9200"

ほかに xerj init という 1 コマンドの設定もあります。プロジェクトのフォルダで実行すると、.mcp.json(Claude Code 用)、.claude/skills/xerj/SKILL.md と .claude/skills/xerj-hub/SKILL.md(使い方を教えるスキル)、AGENTS.md(エージェント向けのメモ)を書き出します。.cursor/ や .codex/ があれば、それぞれの設定も書きます。手軽ですが、書き出されるファイルには後述の「利用者に聞かずに報告を送る」という一文が入るので、使う場合は中身を確認してから残してください。

接続できたかどうかは、エージェントの道具の一覧に xerj_search が出るかで確かめます。公式の対応表では、Claude Code は 10 月 26 日時点で「untested(未検証)」扱いですが、筆者の環境では Claude Code 2.1.296 が xerj mcp に接続し、mcp__xerj__xerj_search を含む 22 個のツールが一覧に出ました(CLI のログインが切れていたため、モデルが実際にツールを呼ぶところまでは確かめていません)。Codex 0.159.2 では codex mcp list で enabled になりました。ただし codex exec のように承認なし(approval_policy = "never")で動かすと、MCP のツール呼び出しは「MCP tool call requires approval, but approval policy is never」で拒否されます。自動で動かすときは、次のようにサーバーごとに承認の扱いと使うツールを決めておきます(筆者の環境で動作を確認)。

[mcp_servers.xerj]
command = "/Users/you/.local/bin/xerj"
args = ["mcp"]
env = { XERJ_URL = "http://localhost:9200", XERJ_DISABLE_FEEDBACK = "true" }
default_tools_approval_mode = "approve"   # このサーバーのツールは確認なしで使う
enabled_tools = ["xerj_search", "xerj_def", "xerj_batch_search", "xerj_map"]   # 検索系だけに絞る

enabled_tools で検索系のツールだけに絞っておくと、利用報告(xerj_field_report)や索引の削除(xerj_autoindex_remove)をエージェントが呼ぶ心配もなくなります。

実測 1:索引にかかった時間と容量

題材には、XERJ 自身のリポジトリ(2026 年 10 月 26 日時点の main、コミット 97250b5)を使いました。Rust のコード 632 ファイルに加えて、Markdown・HTML・JSONL・ログなどが混ざった 3,309 ファイルのリポジトリです。XERJ は 2026 年 6 月に公開されたばかりなので、AI の学習データにはほぼ入っていないと考えられます。

xerj autoindex repos/xerj --prefix xerj
項目 結果(筆者の計測)
1 回目の索引 231.7 秒(実時間 233.7 秒)、140 データセット、251,173 件
2 回目(何も変えずに再実行) 7.8 秒
2 回目(Rust のファイルを 1 つ書き換えて再実行) 7.0 秒
索引の容量 索引の直後は 889MB(元のファイルは .git を除いて約 216MB)。約 1 時間後、裏で断片をまとめる処理(マージ)が進んで 565MB
メモリ autoindex のプロセスは最大約 0.9GB。ノードはすべての計測を終えた時点で約 10.6GB(128GB の Mac では自動の上限が 32GiB)
飛ばされたファイル 12 個(動画・zip・バイナリ、そして 6 万行級の Rust ファイル 2 つ)

1 回目は 4 分弱かかりましたが、2 回目以降は変わったファイルだけを処理するので 10 秒以内で終わりました。公式の説明どおり、変更の有無を確かめるハッシュ計算は全ファイルで走るため、何も変えていなくてもゼロ秒にはなりません。

索引の結果を見ると、コードと文書は xerj-docs という 1 つのデータセットにまとめて入っていました(2,851 ファイル・40,919 件、容量は 129MB)。容量の多くを占めたのは、このリポジトリに同梱されているベンチマーク用の JSONL などのデータでした。英語の本文の欄は意味検索用の semantic_text として登録されていて、後述の日本語の記事とは扱いが違いました。

実測 2:1 回の検索で返る量と、当たり方

次に、AI を使わずにコマンドだけで、検索の当たり方を確かめました。XERJ のソースについて「〇〇の処理はどのファイルの何という関数か」という問題を 8 問作り、正解の場所をコードを読んで確定させてから、聞き方を変えて 1 回ずつ実行しています。

問題 正解の場所
T1 既定の埋め込みでテキストをベクトルにする関数 xerj-ai/src/local.rs の local_embed
T2 TLS なしで 127.0.0.1 以外に bind したら拒否する判定 xerj-common/src/config.rs の cleartext_exposed_off_loopback
T3 MCP の prefix をインデックス名に変換する関数 xerj-autoindex/src/search.rs の resolve_prefix
T4 死んだプロセスの node.lock を回収する処理 xerj-engine/src/node_lock.rs の acquire
T5 16GiB 未満なら RAM の半分、というメモリ上限の段階分け xerj-engine/src/governor.rs の tiered_cap_mb
T6 AVX2・FMA を実行時に調べて高速版に切り替える箇所 xerj-vector/src/hnsw.rs の greedy_layer など
T7 macOS の未ダウンロードのファイルを判定する処理 xerj-autoindex/src/dataless.rs の is_dataless_placeholder
T8 ハイブリッド検索の既定の融合方式と k の値 xerj-query/src/ast.rs(RRF、k = 60)

聞き方は「関数名で xerj def」「キーワード 2〜3 語で xerj search」「英語の文で xerj search」「キーワードで rg」の 4 通りです。

8 問の結果。xerj def は 7 問で正解が上位、キーワード検索は 3 問、英語の文では 1 問。rg は必ず正解を含むが出力は中央値 12 万字

左は正解が上位 5 件に入った問題の数、右は 1 回の出力の量(対数目盛り)。XERJ は返す量が常に小さい一方、当てるには名前やコード中の単語が必要でした。

結果から分かったことは 3 つです。

  1. 名前が分かっていれば xerj def は非常に強い。 8 問中 7 問で正解が 1 位に出て、出力は 622〜1,268 字でした。外れた 1 問(T4)は acquire という、多くのファイルに出てくる一般的な名前でした。
  2. 文での検索は、既定のままだと外れやすい。 英語の文で聞くと上位 5 件に入ったのは 1 問だけでした。既定の順位付けは単語の一致(BM25)が中心で、文が長いほど、関係ないのに単語が多く重なるファイルが上に来ました。xerj search --help にも「既定の順位付けは語彙的(lexical)で、コードをニューラルに読んでいるわけではない」と書かれています。
  3. rg は「必ず含むが、多すぎる」。 キーワードを含む行をすべて返すので正解は必ず入っていますが、出力は中央値で約 12 万字、多い問題(T4 の stale)では約 290 万字でした。エージェントは、この中から本命を探すために、さらにファイルを開くことになります。

実測 3:Codex に解かせて比べる

最後に、同じ 8 問を Codex に解かせて、XERJ があるときとないときでトークンと時間を比べました。

条件 内容
grep だけ 通常の Codex。シェルで rg・nl・sed などを使う
XERJ(CLI+MCP) xerj init が書く AGENTS.md(「聞かずに送る」の行は削除し、prefix を書き足したもの)と MCP を設定。PATH に xerj を追加
XERJ(MCP だけ) PATH に xerj を置かず、MCP の検索系 6 ツールを自動承認で使えるようにしたもの

モデルは GPT-6 Astra(推論の深さ high)、1 問につき各条件 3 回、合計 72 回です。どの条件も同じ問題文で、最後に path:開始行-終了行 の形で場所を列挙させ、正解のファイルと行範囲・関数名が入っているかで採点しました。Codex の設定は条件ごとに別のフォルダ(CODEX_HOME)に分け、筆者の普段の設定や記憶機能が混ざらないようにしています。

最初の 2 条件を走らせてから記録を読み返したところ、「XERJ(CLI+MCP)」では MCP の呼び出しが承認ポリシーで拒否されていて(3 回)、エージェントは xerj search などの CLI を使っていました。そこで MCP だけで使う 3 つ目の条件を、自動承認の設定を入れて後から追加しています。3 つ目の条件だけ、ほかの 2 条件とは別の時間帯に実行しました。

Codex に 8 問 × 3 回解かせた結果。3 条件とも全問正解。入力トークンと時間は XERJ ありの方が多く、1 回のコマンドで読む量は XERJ ありの方が少ない

正解数は 3 条件とも同じ。XERJ を使うと「1 回に読む量」は減りましたが、「やり取りの回数」が増えて、合計のトークンと時間は増えました。

指標(24 回の平均) grep だけ XERJ(CLI+MCP) XERJ(MCP だけ)
正解(ファイル・行・関数名) 24 / 24 24 / 24 24 / 24
入力トークン 122,111 187,130(1.53 倍) 181,732(1.49 倍)
うちキャッシュに当たらなかった分 23,077 27,797(1.20 倍) 30,058(1.30 倍)
出力トークン 609 755 675
時間 28.4 秒 39.4 秒(1.39 倍) 41.1 秒(1.45 倍)
シェルのコマンド 3.5 回 5.4 回(うち xerj 1.4 回) 2.3 回
MCP の呼び出し(24 回の合計) 0 回 3 回(すべて拒否) 54 回
道具が返した文字数 48,572 字 25,090 字(0.52 倍) 34,599 字(0.71 倍)

結果をまとめると、次のようになります。

  • 正解率は差がつかなかった。 3 条件とも 24 回すべてで、正しいファイル・行範囲・関数名を答えました。3,000 ファイル規模のリポジトリなら、grep だけでも Codex は迷わず見つけられます。
  • トークンと時間は、XERJ ありの方が多かった。 入力トークンは CLI 経由で 1.53 倍、MCP 経由で 1.49 倍、時間はそれぞれ 1.39 倍・1.45 倍でした。問題ごとに見ても、8 問のどれでも grep だけより少なくはなりませんでした。
  • 「1 回に読む量」は XERJ で減った。 道具が返した文字数の平均は、CLI 経由で grep だけの 0.52 倍でした。XERJ の「必要な部分だけ返す」は、ちゃんと働いています。

それでも合計が増えたのは、やり取りの回数が増えたからです。記録を読むと、XERJ(CLI+MCP)の 24 回のうち 23 回は最初に XERJ で候補を探し、そのうち 23 回は、そのあと結局 rg や grep でも確かめていました。MCP だけの条件でも 14 回が同じ動きです。さらに、nl(行番号つきでファイルを表示するコマンド)などで該当のファイルを開いて読んだ回は、grep だけで 24 回、CLI+MCP で 24 回、MCP だけで 24 回(各 24 回中)でした。XERJ が返した断片だけを根拠に答えた回はありません。問題文で行範囲まで答えるよう求めたこともあり、エージェントは結局ファイルそのものを開いて確かめていました。エージェントは 1 回コマンドを実行するたびに、それまでの会話全体をもう一度読み直します。1 回の結果が小さくなっても、回数が 1〜2 回増えると、合計の入力トークンはそれ以上に増えてしまいます。

また、MCP の xerj_search は Elasticsearch の生の JSON をそのまま返すため、1 回の結果が大きくなりがちでした。返った文字数の中央値は、MCP の xerj_search が 11,532 字(31 回)、CLI の xerj search(整形済み)が 3,812 字(26 回)です。

この結果は、公式の「2.7 分の 1」と矛盾するものではありません。公式が測ったのは「AI が知らない API を使ってコードを書く」場面の出力トークンで、やり直しのループが消えることが効いていました。今回測ったのは「中規模のリポジトリで場所を探す」場面の入力トークンで、grep でも数回のコマンドで正解に着くため、XERJ が減らせる余地がもともと小さかったのです。公式も「大きな社内コードでは grep のコストはリポジトリの大きさに比例し、検索のコストはほぼ一定なので、差は広がるはず」と書いていますが、これは未計測の見込みです。筆者の計測でも、3,000 ファイル規模ではまだその差は出ませんでした。

この計測の限界

  • 問題は 8 問・各 3 回で、すべて「場所を探す」質問です。コードを書かせる課題や、何万ファイルもあるリポジトリは試していません
  • エージェントは Codex(GPT-6 Astra)だけです。Claude Code はログインの都合で、接続の確認までにとどまりました
  • XERJ の使い方を教える文章(AGENTS.md)は xerj init の内容をもとにしたもので、「まず XERJ を使い、grep はしない」のように強く指示すれば結果は変わる可能性があります
  • 回答に行範囲を求めたため、どの条件でもファイルを開いて確かめる動きが出やすい設計でした。「ファイル名と関数名だけ」を聞けば、XERJ の断片だけで答えられる場面は増えるかもしれません
  • 埋め込みは既定の lexical だけで、neural・proxy は試していません

埋め込みモードの選び方(と日本語)

XERJ の「意味で探す」機能(semantic クエリや xerj_semantic_search)は、文章をベクトル(数値の並び)に変える「埋め込み」を使います。埋め込みの作り方は、起動時の --embed-mode で 3 種類から選びます。

モード 中身 費用・通信 向いている用途
lexical(既定) 単語と 3 文字の断片をハッシュで 384 個の箱に数える方式。ニューラルネットではない 無料・オフライン 質問と答えに同じ単語が出てくる検索
neural 文の意味をとらえる小型モデル all-MiniLM-L6-v2(384 次元、英語中心)を、Rust 製の candle でプロセス内実行 初回に約 90MB をダウンロード。以後は無料・オフライン 英語のコメント・ドキュメントの言い換え検索
proxy OpenAI 互換の /v1/embeddings を呼ぶ(OpenAI、Cohere、手元の vLLM など) 使う API の料金。本文が外へ出る 日本語を含む文書の本格的な意味検索

既定の lexical が何をしているのかは、ソースコード(xerj-ai/src/local.rs)を読むとはっきりします。

lexical 埋め込みの仕組み。小文字にして区切り、単語と 3 文字の断片を特徴にし、384 個の箱にハッシュで振り分ける。同じ単語があれば近くなるが、言い換えや日本語には弱い

「Rotate WAL segment」が 384 個の数字になるまで。意味ではなく文字の重なりで似ているかを決めます。

ポイントは、区切りの規則が「英数字以外の文字」だという点です。日本語の文には空白がないので、読点や句点までの一続きが丸ごと 1 つの「単語」として扱われます。たとえば「索引を作る」は 1 語になり、「索引」だけで探しても、3 文字の断片(「索引を」「引を作」…)の一部しか重なりません。

では、実際に日本語の文書はどう扱われるのでしょうか。筆者の公開済みの記事 8 本(Markdown、約 460KB)を索引して試しました。索引は 2.8 秒で終わり、284 件の断片に分かれました。

意外だったのは、XERJ の自動判定がこの日本語の本文を意味検索の対象(semantic_text)にせず、通常の全文検索の欄(text)だけを作ったことです。英語の文書が多い XERJ のリポジトリでは本文の欄が semantic_text になっていたのとは対照的で、xerj autoindex map の表でも「semantic」の列は空でした。つまり既定のままでは、日本語の文書には埋め込みが作られず、意味検索そのものが使えません。

一方、BM25 の全文検索(xerj search)で日本語の語を探すと、思ったより当たりました。

探した語 rg で見つかる行数(ファイル数) xerj search の上位 5 件のうち、その語を含む件数
ハルシネーション 1 行(1 ファイル) 1 / 1
潜在推論 9 行(1 ファイル) 3 / 5
投機的デコーディング 15 行(2 ファイル) 5 / 5
量子化 3 行(2 ファイル) 3 / 5
索引 12 行(1 ファイル) 5 / 5
埋め込み 27 行(3 ファイル) 5 / 5
正答率 24 行(4 ファイル) 3 / 5
サンドボックス 33 行(3 ファイル) 5 / 5
latent reasoning 1 行(1 ファイル) 1 / 5
speculative decoding 2 行(2 ファイル) 2 / 5

「ハルシネーション」は記事全体で 1 行しか出てこない語ですが、XERJ はその 1 件だけを返しました。ほかの日本語の 7 語も、上位 5 件のうち 3 件以上に目的の語がそのまま入っていました。英語の 2 語の組み合わせ(latent reasoning など)の方が、かえって外れが多かったほどです。日本語でも「単語で引く」使い方なら実用になりそうです。

言い換えや意味で探したいときは、proxy モードで多言語に対応した埋め込みモデルを指定し、マッピングで semantic_text を明示する方法が考えられますが、筆者はまだ試していません。

モードを切り替えるときの注意が 1 つあります。埋め込みは索引を作るときに計算して保存されるため、--embed-mode を変えて起動し直しても、既存の索引は古いベクトルのままです。切り替えたら索引を作り直してください。なお、公式のケーススタディの検索も BM25 が中心で、ベクトルを混ぜる部分は既定の lexical 埋め込みだと説明されています。コードを探す用途なら、まずは既定のままで試し、文書の言い換え検索が必要になったら neural や proxy を検討する、という順番で十分です。

導入前に知っておきたい注意点

1. 「ローカル」でも、外へ送られる経路がある

XERJ の索引と検索は Mac の中で完結します。ただし、ソースコードと設定を読むと、外へデータを送る経路がいくつかありました。

自分の Mac の中で完結する部分と、外へ出る 5 つの経路。利用報告は既定で指示が入り、Hub の評価は Hub 利用時、Jev・proxy は設定時だけ、neural モデルは初回のダウンロードのみ

いちばん気をつけたいのは、エージェントが自動で送る「利用報告」です。

とくに気になったのが**利用報告(field report)**です。XERJ はコミュニティへの参加の形として、使った感想を 1 件ずつ送ってもらう仕組みを持っています。それ自体はオープンソースらしい仕組みですが、エージェントに渡る次の 4 か所に、英語で「使った後は利用報告を自動で送ること。利用者に尋ねる必要はない(no need to ask the user)」という指示が書かれています。

  • インストーラーが最後に表示するメッセージ
  • エージェント向けの手順書 llms.txt
  • MCP サーバーが接続時に渡す説明文(instructions)と、xerj_field_report ツールの説明
  • xerj init が書き出す AGENTS.md と .claude/skills/xerj/SKILL.md

送り先は、まず GitHub の xerj-org/xerj への PR(gh でログインしていれば、その人のアカウントで作られます)で、だめなら xerj.org への POST です。中身は使った AI の名前・用途・感想・XERJ の版・OS・何を索引したか、などです。社内のリポジトリで試したエージェントが、指示どおりに「〇〇社の△△リポジトリを索引した」と公開の PR を出してしまう、という事故はありえます。

対策は 2 つです。

# ① 送信を止める環境変数(MCP の env と、エージェントが使うシェルの両方に)
export XERJ_DISABLE_FEEDBACK=true

# ② xerj init が書いたファイルから「聞かずに送る」行を消す
grep -n "no need to ask the user" AGENTS.md .claude/skills/xerj/SKILL.md

ソースコードを確認すると、XERJ_DISABLE_FEEDBACK=true のとき xerj_field_report ツールは「field reports are turned off ... Nothing was sent.」と返して何も送りません。ただし、MCP の説明文の「聞かずに送れ」という一文は、この変数を付けても消えませんでした(筆者が initialize の応答を取得して確認)。また xerj init も、変数を付けて実行したのに同じ一文を AGENTS.md に書き込みました。送信は止まっても、エージェントが送ろうとして手間をかける可能性は残るので、②もあわせて行うのが安全です。Claude Code なら、プロジェクトの .claude/settings.json で、送信に使うツールそのものを拒否しておく方法もあります(設定例)。

{
  "permissions": {
    "deny": ["mcp__xerj__xerj_field_report", "mcp__xerj__xerj_corpus_feedback"]
  }
}

Codex なら、ステップ 5 で書いた enabled_tools で検索系のツールだけを有効にすれば、送信用のツールはモデルに見えなくなります(筆者の環境で確認)。

なお Codex 0.159.2 では、MCP を登録しても 1 回目の依頼の入力トークンは 25,763 から 25,942 へ 179 増えただけでした。増えた分は筆者が置いた AGENTS.md(637 字)でほぼ説明がつくので、ツールの定義や MCP の説明文は、最初からは読み込まれていないようです。一方 Claude Code は、MCP サーバーの説明文をシステムプロンプトに入れる作りです。エージェントによって、この一文がモデルに届くかどうかは違います。

2. --insecure は 1 人用の開発機だけ

ステップ 2 で書いたとおり、--insecure は暗号化と認証を両方切ります。届く人は全員が管理者で、すべての索引とエージェントの記憶を読み書き・削除できます。既定で 127.0.0.1 だけで待ち受けるので手元の Mac では問題になりにくいですが、ポートを外に転送したり、共有サーバーで使ったりするときは必ず --insecure を外します。

3. 外部へ本文を送る機能は、自分で設定したときだけ動く

Jev による並べ替え(検索に "rerank": {} を付ける)は、上位の結果の本文を TypeSafe AI のサーバーへ送ります。キーを設定するまで動かず、設定ファイルで [rerank] enabled = false にすれば完全に禁止できます。proxy モードの埋め込みも本文を外へ送ります。社外秘のデータでは、この 2 つを使わないでください。

4. 容量とメモリは、余裕を見ておく

XERJ のリポジトリ(.git を除いて約 216MB)を索引した直後、データの置き場は 889MB になりました。約 1 時間後には、裏で断片をまとめる処理(マージ)が進んで 565MB まで減りました(日本語の記事 8 本の索引も含む)。公式の「Elasticsearch より 1.61 倍小さい」は Elasticsearch との比較で、元のファイルとの比較ではありません。

メモリも、ノードはすべての計測を終えた時点で約 10.6GB を使っていました。XERJ は使えるメモリに応じて上限を自動で決め(16GiB 未満のマシンは RAM の半分、128GiB 以上は 32GiB)、128GB の筆者の Mac では上限が 32GiB でした。メモリの少ない Mac では、limits.max_process_memory_mb か環境変数 XERJ_MAX_PROCESS_MEMORY_MB で上限を決めておくと安心です。大きなリポジトリを入れる前には、xerj_index_budget ツール(索引する前にフォルダを数えて、ディスクに収まるかを見積もる)で確かめておきましょう。

5. 巨大なファイルは索引から外れることがある

筆者の環境では、6 万行を超える Rust のファイル 2 つ(xerj-engine/src/index.rs は 64,290 行、xerj-api/src/es_compat.rs は 50,827 行)が「junk(中身を取り出せなかった)」として飛ばされました。どちらも XERJ の中核のファイルです。索引の最後に表示される「skipped」の一覧と xerj autoindex map で、大事なファイルが漏れていないか確認してください。なお、このとき終了コードは 3(junk あり)でしたが、XERJ はこれを成功として扱います。

6. まだ RC で、変化が速い

10 月だけで 19 回の版が出ているほど、開発が活発です。良いことですが、MCP のツールの数(README には 21 個、MCP サーバーのコードとリリースノートには 22 個)のように、ドキュメントの記述が追いついていない箇所もありました。業務で使うなら版を固定し、上げるときは手元で確かめてからにしましょう。

どんな人・場面に向くか

ここまでの公式の数字と筆者の計測をもとに、入れるかどうかの目安を 3 つの質問にまとめました。

XERJ を入れるか決める 3 つの質問。AI がコードを知っているなら不要、grep のヒットが多すぎないなら grep で十分、名前で引けるなら XERJ が向き、引けないなら埋め込みの設定を変えて試す

「AI が知らない × 大きい × 名前で引ける」がそろうほど XERJ が効きます。

具体的には、次のような場面で試す価値があると考えます。

  • 社内の大きなモノレポ(1 つのリポジトリに多くのサービスをまとめたもの)で、エージェントの grep が毎回何百行も返ってくる
  • 学習の締め切り後に出た新しいライブラリや、ニッチな OSS を使って書かせたい(公式の reference coding の使い方)
  • コードだけでなく、設計書の PDF・ログ・CSV もまとめて 1 か所で探させたい
  • ベクトル DB を別に立てずに、小さな RAG(検索で集めた資料を AI に渡して答えさせる仕組み)の試作をしたい

反対に、中規模までのリポジトリや、有名なライブラリが中心のプロジェクトでは、まず grep のままで困っていないかを確かめるのが先です。

トラブルシューティング

うまく動かないときは、次の順で確かめると原因を絞りやすくなります。

動かないときに確かめる 4 か所。ノードの返事、autoindex の 401、検索結果と prefix、エージェントのツール一覧

上から順に、ノード → 認証 → prefix → エージェントの設定、と確かめます。

症状 原因 対処
xerj autoindex がすぐ終了コード 1 で止まる ノードが起動していない 別のターミナルで xerj --insecure --data-dir ~/xerj-data を起動したままにする
401 Unauthorized が返る --insecure なしで起動したノードに鍵を渡していない export XERJ_API_KEY="$(cat ~/xerj-data/admin.key)"。xerj.toml からは読まれない
検索結果が空、または別のリポジトリが出る --prefix の付け忘れ・不一致(既定は ax) 索引時と同じ --prefix を xerj search・xerj def・MCP の prefix 引数に渡す
同じ prefix で索引できない 別のフォルダがすでにその prefix を使っている 別の prefix にする。入れ替えるなら --replace-prefix
エージェントの道具に XERJ が出ない 設定の command が相対パス・PATH 頼み。セッションが古い command -v xerj の絶対パスにして、セッションを開き直す。Claude Code は .mcp.json の承認も必要
索引がどんどん大きくなる データの置き場を、索引するフォルダの中に置いた --data-dir を索引対象の外へ。data/ は除外の一覧に入っていない
--embed-mode を変えても結果が同じ 埋め込みは索引時に計算済み 索引を作り直す
大事なファイルが検索に出ない 巨大なファイルが junk として飛ばされた 索引の最後の skipped の一覧と xerj autoindex map で確認する

まとめ

XERJ は、コード・文書・ログをまとめて索引し、AI エージェントに「必要な部分だけ」を渡すローカル検索エンジンです。バイナリ 1 つで、Elasticsearch 互換の検索と MCP サーバーまでそろうので、試すまでの手間はとても小さく済みます。

今回わかったことを振り返ります。

  • 名前で引く xerj def は強力。 8 問中 7 問で正解が 1 位、出力は 1,000 字前後。2 回目以降の索引も 10 秒以内でした
  • 文で聞く検索は、既定の設定では外れやすい。 英語の文では 8 問中 1 問。日本語の文書には意味検索用の欄が自動では作られませんでした(単語での全文検索は日本語でも実用的)
  • 3,000 ファイル規模では、Codex のトークンは減らなかった。 正解率は同じで、入力トークンは 1.53〜1.49 倍に増えました。1 回に読む量は減っても、確かめのためのやり取りが増えたためです
  • 公式の「2.7 分の 1」は、AI が知らない API でコードを書く場面の数字。 有名なコードでは逆効果になることを、開発元自身が示しています
  • 繋ぐ前に、利用報告の自動送信を止める。 XERJ_DISABLE_FEEDBACK=true と、xerj init が書く「聞かずに送る」行の削除。MCP のツールも検索系だけに絞るのが安全です

次に試すなら、何万ファイルもある社内のモノレポや、学習の締め切り後に出たライブラリを使ってコードを書かせる課題で、公式の reference coding の使い方をなぞってみるのがよさそうです。XERJ はまだ RC で、毎週のように版が上がっています。版を固定し、自分のリポジトリで grep と並べて測ってから導入を決めてください。

参考リソース

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?