loci について
lociはコーディングエージェント向けのスコープ別メモリで、統合コーパス全体を検索するのではなく、質問を関連プロジェクトに振り分けます。コード構造と経緯の記録を組み合わせ、対象が不明確な場合は推測せず回答を保留できます。
主な機能
- 01プロジェクトのスコープ間で質問を振り分け
- 02コード構造と経緯の記録を検索
- 03引用付きの回答を返す
- 04候補プロジェクトを示して回答を保留
- 05埋め込みを任意で使ってリポジトリを索引化
活用シーン
- 01現在のプロジェクトで機能の仕組みを調べる
- 02過去の変更理由を思い出す
- 03指定したツールやライブラリを使うプロジェクトを探す
- 04範囲が不明な場合に別プロジェクトの回答を避ける
MCP 対応
任意の`mcp`インストール追加項目でMCPサーバーが加わりますが、入力には公開ツールの詳細が記載されていません。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| k | integer | いいえ | スコープごとのエピソードヒット数。 |
| cwd | string | いいえ | ユーザーの作業ディレクトリ。分かっている場合は必ず渡してください。最も強いルーティングシグナルです。 |
| group | string | いいえ | 対象を1つのプロジェクトグループに限定します。例:'me' または 'client:delroy'。 |
| scope | string | いいえ | 名前でプロジェクトを指定し、ルーティングをバイパスします。 |
| rerank | boolean | いいえ | クロスエンコーダーによる再ランキング。処理は遅くなりますが、並び順が改善されます。 |
| question | string | はい |
詳細
loci
コーディングエージェント向けのスコープ付きメモリー。 2 つのストアを前段で振り分けるルーターです。
question ──▶ router ──▶ ┌── structure store what calls what
(no LLM) └── episode store what happened and why
└─▶ merged, cited answer │
or ABSTAIN ────────────┘
ナレッジグラフは構造を保持しますが、文章は保持しないため、Cookie が破棄された理由は何かという質問には答えられません。逐語的な想起システムは文章を保持しますが、呼び出しグラフがないため、run_agent_turn を呼び出しているのは何かという質問には答えられません。どちらも、書き込むときに名前空間を指定する必要があります。
loci は読み取るときにスコープを決定します。*「ここでは認証がどのように機能するか」と「どのプロジェクトかでこれを解決したことがあるか」*の両方に、同じ質問経路で対応します。変わるのはスコープの集合の大きさだけです。
全体像を一枚で
独立した店が並ぶ通りを想像してください。各店は 1 つのプロジェクトで、それぞれが独自の在庫と帳簿を持ち、共有倉庫にまとめられることはありません。各店は2 つの台帳を持っています。何が何を呼び出すか、そして何が起き、その理由は何かを記録したものです。これらは常に一緒に読むか、どちらも読みません。
店には、重なり合うバッジが付いています。あなたのもの、このクライアントのもの、あのベンダーのもの、このモノレポのもの、といった具合です。1 つのバッジが通りの両端にある店を覆うこともあります。そのため、バッジは階層ではなくラベルです。
あなたが通りを自分で歩き回ることはありません。案内係に尋ねます。案内係が知っているのは、どこに珍しい語があるかだけです。案内係は最大 3 店まで案内するか、ここには該当するものがないと伝え、ありそうな場所を挙げます。
各店には、その店が何であるか(リポジトリ、パッケージ、コマンドなど)を示す看板もあり、何に依存しているかの一覧もあります。看板と一覧を照らし合わせて読むことで、通りはどの店が別の店から購入しているかに答えられます。案内係にどれだけ尋ねても、この質問には答えられません。これは 2 つの店の間の道についての質問であり、案内係が知っているのは店の中にあるものだけだからです。
通りには、まだ 1 つ足りないものがあります。店の中の通路です。そのため、テストフィクスチャ内で見つかった語は、出荷されるコード内の同じ語と同じようには扱われません。
解決する問題
すべてのプロジェクトを 1 つのインデックスに入れると、質問に関係なく、最も大きなプロジェクトが検索結果を独占します。実在する 10 個のリポジトリを統合したグラフを使って測定した結果は次のとおりです。
| 質問 | 関連するノードの返却率 |
|---|---|
| 「localhost で管理者セッションの Cookie が破棄されたのはなぜか?」 | 18% — 31 件中 19 件が最大のプロジェクトから |
| 「ユーザーが[Save as ZIM]をクリックすると何が起きるか?」 | 2% — 62 件中 61 件が最大のプロジェクトから |
| 「予約済みのランディング手順はどのように機能するか?」 | 98% — しかも最大のプロジェクトが実際に答えだった |
3 行目が落とし穴です。答えがたまたま最大のコーパスにある場合、統合検索は非常に優れているように見えますが、そうでない場合は破綻します。同じ失敗が両方向で起きていますが、見えるのは片方だけです。
インストール
配布パッケージは loci-mem です。コマンド、インポート名、プロジェクト名はいずれも loci です。PyPI の loci は無関係な外れ値検出パッケージで、2018 年以降放置されています。python-dateutil をインストールすると dateutil になるのと同じ命名の違いです。
pipx install loci-mem # routing + lexical search, one dependency
pipx install 'loci-mem[all]' # + graphify, local embeddings, MCP server
基本インストールで追加されるのは numpy のみです。ルーティング、BM25、文字 n-gram 行列は、0.6.0 以降、コンパイル済みの Rust 拡張機能になったため、scikit-learn(およびその依存先の scipy)、rank-bm25、joblib は不要になりました。torch、ベクトルデータベース、モデルのダウンロードも、必要に応じて要求しない限り発生しません。
macOS、Linux、Windows 向けのホイールを公開しています。sdist からインストールする場合はソースからビルドされるため、Rust ツールチェーン(1.75 以降)が必要です。
| 追加機能 | 追加される内容 |
|---|---|
graphify | 約 18 言語に対応したコードシンボル抽出 |
embeddings | ローカルの bge-small によるセマンティックランキング |
rerank | クロスエンコーダーによる再ランキング(クエリごとにオプトイン) |
mcp | MCP サーバー |
クイックスタート
pipx install 'loci-mem[all]'
loci setup # scan, graph, index, embed, calibrate -- one pass
loci ask "why was the session cookie dropped on localhost?"
setup で尋ねるのは、自分では判断できないことだけです。プロジェクトが格納されているディレクトリ、見つかったリポジトリのうち自分のものではないリポジトリを登録するかどうか、そしてセマンティック検索のために一度だけモデルをダウンロードするかどうかです。最後に doctor を実行するため、対応できなかったことがあれば、悪い回答を受けて1週間後に気づくのではなく、最後に確認できます。
標準入力がターミナルでない場合、各プロンプトは既定値を選択するため、コンテナ内やエージェントから無人実行しても安全です。ターミナルから実行する場合は -y でも同じ動作になり、--no-graphs / --no-embed / --no-calibrate で個別の手順をあらかじめ指定できます。
手動で同じことを行う場合です。順序は好みではなく依存関係によって決まります。インデックスはグラフを基に構築され、インデックスが embed でエンコードするチャンクを書き出し、calibrate はそれらのベクトルからセマンティック検索のしきい値を調整します。
loci scan ~/code # register every git repo it finds, one scope each
loci graphs # optional: add code symbols (free, no model calls)
loci index # build the routing index + episode store
loci embed # optional: local vectors for semantic recall
loci calibrate # optional: fit routing thresholds to your corpus
loci doctor # what is missing, and the command that fixes it
loci ask "which projects use wrangler and D1?"
loci eval # measure routing accuracy on YOUR corpus
各手順は次の手順が読み取るデータを書き出します。そのため、順序は好みではなく固定されています。
flowchart TD
scan["loci scan ~/code<br/>writes ~/.loci/scopes.json"]
graphs["loci graphs<br/>writes graphify-out/graph.json, one per scope"]
index["loci index<br/>writes ~/.loci/episodes.json, scope_index.json, rankers/"]
embed["loci embed<br/>writes ~/.loci/embeddings.npz"]
calibrate["loci calibrate<br/>writes ~/.loci/calibration.json"]
doctor["loci doctor<br/>writes nothing at all"]
scan -->|"there is nothing to graph or index until a scope exists"| graphs
graphs -->|"the index is built FROM graph.json; index first<br/>and every project routes on prose alone"| index
index -->|"embed encodes episode chunks, and they do not<br/>exist until index has written the store"| embed
embed -->|"calibrate fits a per-scope semantic floor from<br/>those vectors; with none it keeps a default"| calibrate
calibrate -->|"and then reports whatever is still missing"| doctor
順序を間違えても、はっきりとしたエラーにはなりません。インストール自体は動作しますが、検索の精度がひそかに低下します。これを特定するためにあるのが doctor です。
scan は Git リポジトリごとに1つのスコープを登録します。また、Git から各リポジトリの所有者を読み取り、誰が何を所有しているかを表示し、自分のものではないリポジトリを登録する前に確認します。このプロンプトもほかと同様に既定値を選択します。既定値はすべて登録することです。
モノレポは、パッケージごとに1つのスコープに分割することもできます。loci scan または loci setup の --split は、1階層下にある package.json、pyproject.toml、Cargo.toml、go.mod のいずれかを含むディレクトリを対象にします。リポジトリ内の .loci.json でそれ以外の対象を指定でき、フラグの有無にかかわらず設定が適用されます。--split は既定で無効です。その理由は慎重さではなく、計測結果に基づくものです。新しいスコープのエイリアスにはディレクトリ名そのものが含まれ、エイリアスは作業ディレクトリより優先されるからです(6.0 対 4.0)。開発用コーパスでは、glasses/ を含むリポジトリを分割したところ、別のプロジェクトについて手書きされた質問8件が Delroy/glasses に送られました。そのうち7件は分割前には正しく振り分けられており、エイリアスの重みをゼロにすると8件中6件が正しい振り分けに戻りました。同じ実行では、Jekyll の _site/ ビルドディレクトリも独立したスコープになりました。自分で記述した .loci.json には、この問題はありません。サブプロジェクトを意図的に指定しているため、どのような名前で登録されるかを確認できます。
ask は既定で作業ディレクトリを使用します。そうするべきです。プロジェクト名を指定しない質問、たとえば 「これはどのようにデプロイするのですか?」 や 「テストはどう実行するのですか?」 は、cwd があれば 100% 正しく振り分けられます。cwd がなければ回答できず、loci は推測せず、そうした質問すべてに回答を控えます。ここで cwd は同点を解消するためのシグナルではなく、主要なシグナルです。
概念
スコープ。 1つのプロジェクトに1つの名前空間です。登録済みの git リポジトリ、モノレポの1つのパッケージ(--split の下で、または .loci.json で指定)、あるいは明示的に追加したものがスコープになります。スコープが統合されることはありません。
グループ。 スコープに重ねて付けるラベルです。me と vendor:<org> は git の来歴から読み取られ、client:acme などは手動で指定します。また、モノレポ自体の ID は、その内部のすべてのパッケージとモノレポ本体が持つため、--group <monorepo> を指定すると、どのパッケージも所有していないコードにも到達できます。1つのスコープが複数のグループに属することもあります。スコープの集合はフラットなままで、グループ化によってスコープが統合されたり、互いに入れ子になったりすることはありません。
グループが質問に対して行う処理がそのモードであり、モードは2つの異なる問いに答えます。作業ディレクトリ経由で到達した場合、explicit は何もせず、soft(デフォルト)は外部スコープのすべてのエビデンスベースに0.5を掛け、hard はルーティングをメンバーに限定し、最適な回答がメンバー外にある場合は回答を差し控えます。コマンドラインで --group X を指定した場合は、3つのモードすべてでメンバーに限定されます。モードによって決まるのは、最適な回答がメンバー外にある場合の処理だけです。hard は回答を差し控えてその旨を伝え、soft と explicit は最適なメンバーを使って回答します。メンバーシップはスコープレジストリに、モードは groups.json に保存されるため、レジストリ全体を書き換える再スキャンでもポリシーは失われません。実測では、作業ディレクトリを基準にした hard は、外部プロジェクトを明示する質問には発動し(12問中12問)、その語彙だけを含む質問には発動しません(24問中0問)。したがって実際には、現在いる場所よりも --group とプロジェクト名が判断を左右します。
構造ストア。 何が何を呼び出すかを記録します。シンボル、ファイル、参照、file:line の引用付きのトラバーサルが含まれます。graphify がアダプターを通じて提供します。
エピソードストア。 何が起きたのか、そしてその理由を記録します。README とドキュメント、git のコミット本文、docstring とコメントブロック、さらに指定したメモが含まれます。見出しの境界で分割し、書き込む前に編集され、原文のまま保存されます。
ルーター。 モデル呼び出しを使わず、決定論的に、質問がどのスコープに属するかを判断します。重みの高い順に、明示されたプロジェクト名(6.0)、作業ディレクトリ(4.0)、語彙のエビデンス(実測値0.1~1.5)、新しさ(0.15)を手がかりにします。重みは有用性を意味するものではありません。実際の質問の多くはプロジェクト名を挙げないため、実際の質問を最も多く捉える手がかりは作業ディレクトリです。
回答保留。 これは第一級の結果です。ABSTAIN は「ユーザーに尋ねる」という意味で、「最も大きいものを選ぶ」という意味ではありません。どちらのレイヤーも回答を拒否できます。
「いいえ」とだけ答える回答保留では、質問をあなたに委ねてしまいます。そこで、選べなかった候補リストを添えます。各プロジェクトと、それを候補に挙げた用語を示します。
loci ask "how is caching handled?"
ABSTAINED - not enough of the question exists in any project.
candidates: Delroy (handled, caching), odysseus (handled, caching), tensor-serve (caching)
re-run with --scope <name>, or from inside the project directory.
スコープが候補に加わるのは、コーパスの半分以下しか含まない用語を含んでいる場合です。用語を1つ含むだけでは十分ではありません。change、handled、work はほぼすべてのプロジェクトに含まれるため、これらに一致しても主張ではなく偶然にすぎません。開発コーパスでの実測では、この基準によって、3つのプロジェクトを挙げる場合と、レジストリそのものとなる14のプロジェクトすべてをスコア順に列挙する場合とが区別されます。
この基準を満たすものがなければ、表示する候補リストはありません。そのことを伝えるのが回答になります。質問の主題はどこにもインデックスされておらず、スコープを選ぶことは推測の対象を移すだけだからです。
loci ask "xyzzy plugh frotz quuz"
ABSTAINED - not enough of the question exists in any project.
no project holds a distinctive term from this question -- `loci doctor` shows what is not indexed.
アーキテクチャ
クエリの流れ
このファイルの先頭にある図は、要点を簡潔に示したものです。全体像は次のとおりです。
loci ask "why was the session cookie dropped on localhost?"
│
│ --scope NAME jumps straight past routing to the fan-out, and drops the
│ episode gate with it: that gate exists to stop an answer arriving from
│ the wrong scope, and you have just named the right one.
▼
confinement groups.py
│ reads the registry and groups.json. Never the index, never a model.
│ --group X ─▶ eligible = members of X, in ALL THREE modes
│ else cwd ─▶ the strictest group of the scope you are standing in
▼
route router.py
│ reads scope_index.json: token ─▶ {scope: node_df}. One dict lookup per
│ query token. Deterministic, sub-millisecond, no model call.
│
├──▶ ABSTAIN — deictic, no_evidence or out_of_group. Names the cause, lists
│ the scopes with a claim on the question and what each one
│ holds, names the flag that fixes it, and queries nothing.
│ An outcome, not an error.
▼
selected scopes, at most 3 one thread each; neither store is ever
│ queried across a scope boundary
├─── scope ─── scope ─── scope
│ │
│ │ expand the question against THIS scope's postings — a dict
│ │ lookup, so a token the scope does not have cannot be invented —
│ │ then append the tokens of its nearest embedded symbol labels
│ │
│ ├─ structure store graphify query --graph <this scope's graph>
│ │ what calls what, with file:line citations
│ │
│ └─ episode store BM25 + char 3–5 gram + embeddings, fused
│ gated: lexically grounded OR semantically
│ confident, so it can return nothing, and does
▼
merged, cited answer — one block per scope, and `no evidence in this scope`
wherever both of a scope's stores came back empty
2つの段階はそれぞれ独立して回答を拒否できます。ルーターはスコープをまったく選ばないことがあり、選ばれたスコープも何も返さないことがあります。
ルーティングの判断
ファンアウトより上の処理はすべて1つの関数です。コーパス内のすべてのスコープをスコアリングし、その後、固定順で3つの拒否判定を行います。エイリアスまたは CWD のシグナルがある場合、最初の判定は行いますが、残り2つはスキップします。
flowchart TD
Q["question, plus cwd and --group when given"]
CONF{"which group confines this question?"}
E1["eligible = members of X<br/>strict only when that group's mode is hard"]
E2["eligible = members<br/>strict"]
E3["demoted = every scope outside the group"]
E4["nothing confined, nothing demoted"]
BASE["evidence base, for EVERY scope in the corpus<br/>sum(scope-idf x prominence) / query tokens / size^0.15"]
PEN["demoted? base x 0.5<br/>here, before the boosts, so it cannot invert one"]
BOOST["+ ALIAS 6.0, the question names the project<br/>+ CWD 4.0, DEEPEST containing scope only<br/>+ RECENCY 0.15, a tiebreak and no more"]
TOPS["top = best ELIGIBLE scope<br/>top_all = best scope in the whole corpus"]
OOG{"strict, and top_all is outside the group,<br/>and top_all would itself have been routable?"}
A1(["ABSTAIN out_of_group<br/>the best answer is outside the group"])
FORCED{"forced?<br/>top carries an alias hit or the cwd signal"}
ENUM{"enumerative?<br/>which projects / what repos / any of my / where else / have I ever"}
DEIX{"deictic?<br/>this / these / it / its / here / the project / the app / ..."}
A2(["ABSTAIN deictic<br/>the question points at a subject it never names"])
EV{"enough evidence?<br/>ANY ONE of the three is enough"}
A3(["ABSTAIN no_evidence<br/>too little of the question exists in any project"])
SEL{"did the question ask for a SET?"}
SELECT["keep every eligible scope scoring at or above 0.85 x top,<br/>cap at 3, then force in every concentrated-token holder"]
SETSEL["keep every eligible scope clearing 0.8 x the floor ON ITS OWN,<br/>in rank order, cap at 8 -- no ratio cutoff at all"]
OUT(["selected scopes"])
Q --> CONF
CONF -->|"--group X, any mode"| E1
CONF -->|"cwd scope's strictest group is hard"| E2
CONF -->|"cwd scope's strictest group is soft"| E3
CONF -->|"explicit, or no group at all"| E4
E1 --> BASE
E2 --> BASE
E3 --> BASE
E4 --> BASE
BASE --> PEN
PEN --> BOOST
BOOST --> TOPS
TOPS --> OOG
OOG -->|yes| A1
OOG -->|no| FORCED
FORCED -->|yes| SEL
FORCED -->|no| ENUM
ENUM -->|"yes, and it is not picking ONE scope"| EV
ENUM -->|no| DEIX
DEIX -->|yes| A2
DEIX -->|no| EV
EV -->|"summed token evidence at or above the floor<br/>(7.6 shipped, refitted by loci calibrate)"| SEL
EV -->|"at least 4 matched tokens"| SEL
EV -->|"an eligible scope holds a CONCENTRATED token:<br/>held by at most 2 scopes, prominent inside them"| SEL
EV -->|"none of the three"| A3
SEL -->|no| SELECT
SEL -->|yes| SETSEL
SELECT --> OUT
SETSEL --> OUT
この構成で重要な要素は4つありますが、いずれも一見しただけでは分かりません。
forced はシグナルではなく、回避手段です。 エイリアスまたは CWD が一致しても、どちらのエビデンス数にも加算されないため、6.0 または 4.0 だけで勝ったスコープは、後続のゲートからはエビデンスがゼロと見なされます。その代わり、両方のゲートをスキップします。指示語が問題になるのは、ほかに対象を特定する手がかりがない場合だけなので、「それ」と言いつつプロジェクト名も挙げている質問には影響しません。
out_of_group は、グループではなくコーパス全体を基準に判定されます。 コーパス全体の勝者がグループ外にあり、かつそれ自体がルーティング可能な場合にのみ発動します。後者の条件がなければ、ほか2つの理由が完全に無視されてしまいます。語彙に一致しない質問では、すべてのスコープのスコアがほぼゼロになり、0.15 の新しさによるタイブレークで選ばれたスコープが勝者になるためです。
3つのエビデンスゲートは、異なる質問の形で失敗するため、OR 条件で結合されています。 珍しいシンボルについての短い質問はエビデンスが多くカウントが少ない一方、よく知られたサブシステムについての長い質問はその逆になります。3つすべてを要求すると、どちらの場合も棄権してしまいます。
列挙では、ほかのゲートとはすべて逆の判定になるため、独自の選択ルールを使います。 上記の各ゲートは「1つのスコープに十分なエビデンスがあるか」を判定しますが、複数のプロジェクトで共有される対象についての質問では、構造上、エビデンスがそれらに分散します。そのため、ある用語を本当に共有するプロジェクトが増えるほど、すべてが候補として返される可能性は低くなります。実測では、which of my projects use Cloudflare workers or D1? に対して、該当する2つの所有者のうち1つしか返されませんでした。スコアは 2.098 対 1.0867 で、必要な 0.85 の比率には 1.783 が必要でした。
逆になるのは比率の部分です。これは最上位のスコープからの距離を測りますが、列挙では最上位のスコープは、その用語を最も多く含む所有者にすぎません。set モードでは比率をなくし、各スコープについて同じルーティング可能性の判定を個別に行い、基準値を 0.8 に下げます。集合の一員は、単独の回答よりエビデンスが少なくても正当だからです。
列挙も指示語と同様に文法的な特徴なので、同じ方法で検出します。しきい値は設けず、限定された種類のマーカーを使います。また、列挙は指示語よりも優先されます。「このパターンはほかにどこに現れますか?」 は対象を指し示すと同時に、コーパス全体を対象にしています。指示語のルールがあるのは、指し示すだけの質問では1つのスコープを選べないためです。列挙を求める質問では、1つだけを選んでいるわけではありません。
クエリから枠組みを示す名詞を取り除きます。これは、指示語についての知見をもう一段掘り下げたものです。projects は足場であって、語彙ではありません。これは見た目だけの処理ではありません。開発コーパスでは、projects を含むスコープはちょうど2つのため、識別力のあるエビデンスとしてその2つに加算され、「which of my projects」で始まるすべての回答に両方が引き込まれていました。これを取り除いたところ、負のファミリーの棄権率は 66.7% から 88.9% に上昇しました。
loci ask "which of my projects use Cloudflare workers or D1?"
# ENUMERATED -> 3M1RY33T.github.io, urthreads
スコープとグループ
グループは、フラットなスコープ集合に重なり合うラベルを付けたものです。ツリー構造は一切なく、何かが統合されることもありません。
scope the groups it carries
─────────────────────────── ──────────────────────────────────────
delroy monorepo me delroy
delroy/glasses sub-scope me delroy
delroy/client sub-scope me delroy client:acme
acme-api repo me client:acme
vendorlib repo vendor:someorg
me, vendor:someorg read from git provenance by `scan` or `groups infer`
delroy computed by `scan` from the filesystem: a repository
that splits gives its own id to every sub-project
inside it, and to itself
client:acme asserted by hand, with `loci group add`
5つのスコープと4つのグループがあります。delroy/glasses は delroy の 内側 に保存されているのではなく、me と同じように delroy というラベルを持っています。client:acme は、モノレポのサブプロジェクトと無関係なリポジトリにまたがっており、階層ではまったく表現できません。2つのサブスコープが存在するのは、そのリポジトリで .loci.json に定義されているか、--split を指定してスキャンされた場合だけです。どちらにも該当しない場合、delroy は1つのスコープとなり、包含グループは存在しません。親スコープは、自身のコレクションからサブスコープのサブツリーを除外するため、ファイルが二重に数えられることはなく、どちらの語彙ももう一方のトークンで膨らむことはありません。
質問に対してグループがどのように作用するかを示すのがモードです。グループにどのようにたどり着いたかによって、モードが答える質問は2つに分かれます。
reached through your cwd named with --group X
──────────────── ───────────────────────────── ────────────────────────
explicit nothing confines to X's members
soft (default) every outside scope's confines to X's members
evidence base × 0.5
hard confines to members, and confines to X's members,
abstains when the best and abstains when the
answer is outside best answer is outside
右側の列は直感に反する部分なので、主張ではなく実測値を示しています。3つのスコープを使ったフィクスチャでは、alpha と beta が team グループに属し、vend はその外にあります。1つの質問に対して4つのポリシーを適用します。
loci route "how does the gizmo parser emitter handle sprocket calibration"
unconfined -> vend, alpha
--group team explicit -> alpha
--group team soft -> alpha
--group team hard -> ABSTAIN (out_of_group); candidates: alpha (sprocket), beta (calibration)
3つのモードすべてで、グループ指定なしの場合に勝ったスコープである vend は除外されました。--group は、答えがここにあるとあなたが指定するものです。答えがなかった場合に何が起きるかだけをモードが決めます。hard のショートリストが、その上にある選択結果より広いのは意図的です。これは棄権によってどれを選ぶか決められなかったスコープであり、そこから回答を選ぶという意味ではありません。beta が含まれているのは、calibration が beta だけに含まれる用語だからであり、回答に含まれないのは、それしか持っていないからです。
一方、cwd 経由の場合、soft は順位を下げますが、対象を限定しません。--group を指定せずに alpha 内から同じ質問をすると、alpha, vend が返されます。vend のスコアは3.39で、alpha の6.18に対して0.85の拡張範囲を大きく外れています。それでも、gizmo、parser、emitter を持つのが vend だけであり、集中度の高い層では、共有語または希少語を持つすべてのスコープが回答に含まれるため、結果に戻ってきます。順位が下がることと、除外されることは同じではありません。
コマンド
| コマンド | 目的 |
|---|---|
loci setup [dirs…] | スキャン、グラフ作成、インデックス作成、埋め込み、キャリブレーションを1回で実行します(-y、--no-embed、--split) |
loci update [dirs…] | 登録済みの対象について、グラフ、インデックス、ベクトル、キャリブレーションを更新します(--no-scan、--force) |
loci scan <dirs> | git リポジトリを検出し、スコープとして登録します(--split) |
loci add <path> | スコープを1つ明示的に登録します(--alias、--glob) |
loci scopes | 登録済みのスコープを一覧表示します(--group) |
loci uses | 引用元とともに、プロジェクトが別のプロジェクトを参照している箇所を表示します(--refresh) |
loci groups | グループ、解決済みのモード、および各グループのスコープ数を一覧表示します |
loci groups infer | git の来歴に基づき、すべてのスコープに me または vendor:<org> のラベルを付けます |
loci group set <group> --mode | グループによる対象の限定度を設定します:explicit、soft、hard |
loci group add|rm <scope> <group> | 1つのスコープの所属グループを編集します |
loci graphs [scope…] | graphify を使って、不足している構造グラフを作成します |
loci index | ルーティングインデックスとエピソードストアを作成します(--force) |
loci embed | エピソードのチャンクをローカルでエンコードします(--model) |
loci calibrate | コーパスに合わせてエビデンスの下限を調整します(--show) |
loci route "…" | 質問のルーティング先を表示します(--explain、--group) |
loci ask "…" | ルーティング後、両方のストアに問い合わせます(--scope、--group、--fast、--rerank) |
loci doctor | スコープごとのカバレッジ不足と、その解消方法を表示します |
loci eval | コーパス上でルーティングを評価します(--misses) |
loci skill install | AI クライアント用の /loci スキルをインストールします(--dir、--print) |
loci mcp | stdio 上で MCP サーバーを実行します |
loci index は、ファイルと git HEAD に変更がないスコープを再利用するため、コミットフックから実行しても、再インデックスのコストは十分に低く抑えられます。
最新状態の維持
自動で更新されるものはありません。ウォッチャーもデーモンも、インストールされた git フックもありません。更新のたびに、コマンドを実行する必要があります。
loci update # the whole chain, for everything registered
これは既存のインストールに対する setup なので、何も尋ねず、新しい設定も追加しません。すでに埋め込みがあるため再エンコードし、すでにしきい値が調整されているため再調整します。また、前回のスキャン対象を再スキャンするので、その後に作成されたリポジトリも見落とされずに登録されます。これが scopes.json の roots キーの役割であり、loci update ~/work を実行すると別のルートが追加されます。
無条件に再構築するのは構造グラフだけです。これは、loci でそれ以外の古さが表面化しない唯一のものだからです。loci graphs はグラフがない場合に作成し、すでにグラフがあるスコープはすべてスキップするため、コードが移動したプロジェクトでは、登録時に取得したシンボルに基づくルーティングがそのまま使われます。それ以外はすべて状態がわかります。index はフィンガープリントが変わったスコープを再解析し、doctor はストアと一致しなくなった埋め込みを示します。グラフの再構築は負担になりません。graphify はコードに変更がなければグラフを書き換えないためです。実測では、連続して 2 回目に実行しても graph.json のバイト列は同一で、mtime も変更されません。loci 自身のフィンガープリントはこの mtime を読み取るため、グラフを更新しても、変化のなかったスコープの再インデックスは発生しません。
手動で同じことをする場合も、依存関係に沿った順序は変わりません。
loci graphs --all # `loci graphs` alone would skip every existing graph
loci index # unchanged scopes are reused, not re-parsed
loci embed # a full re-encode; skipping it silently disables
# semantic search for every scope whose chunk count moved
loci calibrate # the floor measures shared vocabulary, which just changed
update では対応できないことが 2 つあります。ディスクから削除されたプロジェクトはレジストリに残ります。エントリーには手動で設定したグループとエイリアスが含まれており、ディレクトリがアンマウントされただけの可能性もあるため、削除ではなく報告されます。また、長時間稼働する loci mcp サーバーは、起動時に読み込んだ調整済みランカーとベクトルをメモリに保持するため、再構築後に再起動してください。
AI クライアント内から使う
loci skill install # -> ~/.claude/skills/loci/SKILL.md
これは 8 つのコマンドで一覧を埋め尽くすのではなく、サブコマンド付きの 1 つのスキルをインストールします。
/loci "<question>" ask memory; routes on your working directory
/loci update refresh everything registered
/loci doctor coverage gaps per project, and the fix for each
/loci scopes what is registered, and the group each is in
/loci route "<question>" where a question routes, and why it abstained
/loci add <path> register one project
/loci setup [dirs…] first run
/loci <anything else> passed through to the `loci` CLI verbatim
簡潔さを保つのがパススルールールです。index、embed、graphs、calibrate、eval、groups はすべて、クライアントからドキュメントを二重に用意しなくても使えるため、スキルに記載のないフラグとのずれが生じません。スキルに記載されている内容は検証されます。テストが Usage ブロックを解析し、そこに記載されたコマンドが存在しなくなると失敗します。
このスキルは dotfiles リポジトリではなくパッケージに含まれています。CLI で変更される可能性のあるフラグや棄権理由を説明しているためです。アップグレード後に loci skill install を実行するだけで更新できます。--print を使うと、~/.claude/skills を読み込まないクライアント向けに標準出力へ書き出せます。
CLI からはわかりにくいことを、エージェントに 2 つ教えます。プロジェクトのルートから実行してください。cwd は主要なルーティングシグナルなので、間違ったディレクトリから質問すると、ディレクトリを指定しない場合よりルーティングの精度が下がります。そして、ABSTAINED も回答です。理由には次にすべきことが示され、その下の候補リストには質問を担当しそうなプロジェクトと、それらが候補になった理由となる用語が示されます。それなのに、言い換えて再試行するエージェントは、表現ではなく語彙の問題に対処するためにターンを消費しています。候補リストをまったく伴わない棄権だけは、言い換えても解決できません。コーパスに、質問を特徴づける用語が含まれていないからです。
MCP
loci mcp は stdio で通信するため、クライアントが起動します。自分で実行する必要はありません。
Claude Code:
claude mcp add loci --scope user -- loci mcp
claude mcp list # loci: ... - ✔ Connected
Claude Desktop の claude_desktop_config.json:
{
"mcpServers": {
"loci": { "command": "loci", "args": ["mcp"] }
}
}
loci がバージョン管理された環境にある場合は、command に絶対パスを指定してください。pyenv の shim や pipx の venv は、クライアントが利用できないシェルを使ってパスを解決するためです。動作しているシェルで which loci を実行し、表示されたパスを貼り付けてください。
ツールは数十個ではなく、ask、scopes、doctor の3つです。主な利用者はエージェントであり、ツールの数が多いとオーケストレーションがモデルに委ねられ、ホップごとに1ターン消費されます。
これがなければ、サーバーは役に立ちません。mcp extra によってインストールされても、何からもアクセスできず、エージェントが尋ねる質問はすべて代わりに CLI を経由します。その結果、質問ごとにプロセスとモデルの起動コストがすべてかかり、サーバーが提供する起動時のウォームアップも利用できません。
クライアントが cwd を把握している場合は、ask に渡してください。また、コマンドラインの --group と同じように範囲を絞るには、group を渡してください。サーバーは起動時に埋め込みモデルと最大のスコープのランカーを読み込むため、ユーザーを待たせて約4.4秒の起動時間をかけることなく、エージェントの最初のツール呼び出しが高速になります。
独自のコーパスで測定する
以下の精度の数値はすべて、ある人のリポジトリを使って調整・測定したものです。定数の調整時は10個、現在は14個です。これは本来、信頼するしかない主張ですが、この2つの数値のずれによって、動作ファミリーのスコアは57ポイント低下しました。
loci eval # a few seconds, no labelling, no setup
loci eval --misses # and what it got wrong
正解が構造上明らかな質問だけを行います。
| ファミリー | 正解が分かる理由 |
|---|---|
| 指示語+cwd | スコープ内から質問するため、回答はそのスコープです |
| 指示語、cwd なし | どのプロジェクトかを示す情報がないため、回答を保留するのが唯一の正解です |
| 回答不能 | ソフトウェアコーパスでは回答できない、でたらめな質問です |
| シグネチャ | 各スコープ固有の、最も特徴的な語彙から作成します |
10 scopes | random guessing would score 10.0%
family n correct scopes what it measures
deictic + cwd 80 100.0% - should route to the scope you are in
deictic, no cwd 8 100.0% - should abstain; nothing says which project
unanswerable 6 100.0% - should abstain; no corpus can answer these
signature 20 100.0% 1.0 are your scopes distinguishable from each other?
シグネチャファミリーは上限値です。各スコープについて、そのスコープ内で最も珍しい単語を質問に使うため、共通の語彙で表現された実際の質問では、これより低い結果になります。ここでスコアが低いのは、プロジェクトに関する事実であり、必ずしも調整で解消すべき不具合ではありません。
キャリブレーション
ルーティングでは、互いに独立した3つのシグナルを判定し、どれか1つを満たせば十分です。非常に識別力の高いトークンが1つあること、トークンの証拠の合計が十分であること、または一致したトークンの数が十分であることです。これらは異なる種類の質問で失敗します。珍しいシンボルについての短い質問では証拠が強くトークン数は少なくなり、なじみのあるサブシステムについての長い質問ではその逆になります。そのため、3つすべてを必須にすると、どちらの場合も回答を保留してしまいます。
証拠の下限は、プロジェクト間でどれだけ語彙が共有されているかに左右されるため、コーパスに依存する唯一の値です。loci calibrate は同じ自動ラベル付き質問からこれを調整し、判定帯がそもそも分離できているかどうかを報告します。
evidence floor 7.17
fitted from 149 routable + 14 unroutable questions, none hand-labelled
classifies 84.0% of its own samples correctly
! The bands OVERLAP -- some questions that should route score no higher than
questions that should not, so no threshold separates them cleanly.
重複の警告は、プロジェクトに関する情報であり、欠陥ではありません。プロジェクト間で十分な語彙が共有されているため、証拠だけでは常に区別できず、cwd や --scope がより大きな役割を担う必要があるということです。
読み取る内容と書き込む内容
すべてのデータはマシン上に残ります。クエリ処理には LLM を使わず、ルーティングはトークンごとの辞書参照で行います。埋め込みを有効にした場合も、ローカルで実行されます。
スコープごとに収集するもの:README*、docs/**/*.md、ソースファイル(docstring とコメントブロック用)、および git log。リポジトリ外の絶対パスを含め、ほかのものは loci add --glob で追加できます。
リポジトリ内で loci が読み取る設定は .loci.json だけです。読み取られるのは、そのファイルを自分で作成した場合に限ります。このファイルでは任意の深さのサブプロジェクトを指定でき、--split とは異なり、スキャンのたびに適用されます。
{"scopes": [{"path": "client"}, {"path": "services/api"}]}
パスはリポジトリのルートからの相対パスで、リポジトリの外には出られません。不正な形式のファイルは無視されます。余計なカンマがあるだけでスキャンを中断するより、宣言を見逃すほうがましです。このファイルを書き込む処理はありません。作成するのはユーザーです。
loci がリポジトリ内に書き込むのは graphify-out/graph.json だけです。これを書き込むコマンドは 2 つあります。ほかの処理を行わない loci graphs と、5 ステップ中のステップ 2 で作成を提案し、デフォルトで「はい」を選ぶ loci setup です。ターミナルがない場合も同様で、すべてのプロンプトでデフォルトが選ばれます。loci setup --no-graphs を指定すると作成をスキップし、それ以外のコマンドは読み取りを行います。
つまり、リポジトリ内で関係するファイルはちょうど 2 つで、それぞれ逆方向を指しています。
<repo>/graphify-out/graph.json loci writes it, on request. You never do.
<repo>/.loci.json you write it. loci never does.
そのほかのすべては ~/.loci(または $LOCI_HOME)に保存されます。プロジェクトごとにディレクトリを作るのではなく、1 つのディレクトリを使います。ルーティングはスコープをまたいで判断するため、「これはどのプロジェクトに関するものか」に答えるには、すべてのスコープの語彙を同じルックアップで参照する必要があります。プロジェクトごとに N 個のインデックスを作ると、低コストな問いが高コストになってしまいます。
~/.loci/ what it holds written by
├── scopes.json the scope registry, and scan, add, group,
│ the roots it was scanned update
│ from, for `loci update`
├── groups.json group policy: default mode group set
│ and any mode set per group
├── scope_index.json routing index, the token index
│ -> {scope: node_df} map
├── episodes.json the episode store: chunks index
│ verbatim, already redacted
├── rankers/<scope>.lex fitted BM25 + char-gram index
├── embeddings.npz chunk and symbol vectors embed
├── .symbols-<scope>.json symbol labels, in the embed
│ order embeddings.npz has
├── calibration.json the fitted evidence floor calibrate
│ + a semantic floor / scope
└── .index.lock advisory build locks, so index, embed
.embed.lock two builds cannot inter-
leave their writes
scopes.json はマシンが管理し、スキャンのたびに全体を書き換えます。一方、groups.json はユーザーが管理し、スキャン後も保持されます。そのため、メンバーシップはレジストリに、モードはポリシーに保存され、ファイルも 2 つに分かれています。
マスキング
収集したデータはすべて、書き込む前に loci.redact を通過します。そのため、誤ってコミットされた認証情報がエピソードストア、ベクトル、ランキング処理に渡ることはありません。
秘密情報を保持するためのファイルは開きません。対象は .env*、.dev.vars、*.pem、*.key、id_rsa、secrets.*、credentials.json です。さらに、各チャンクをスキャンし、AWS キー、GitHub/Slack/Google/Stripe/OpenAI/Anthropic のトークン、秘密鍵ブロック、JWT、user:password@host 形式の接続文字列、ベアラートークン、および秘密情報らしい名前に割り当てられた長い不透明な値を検出します。
チャンクは単一のコンストラクターを通じて作成されるため、新しい収集経路でマスキングを忘れることはありません。秘密情報の代入では、キー名を残して値だけを削除します。そのため、チャンクは意味の通る内容を保ち、引き続きルーティングにも使えます。
client_secret = "aVeryLong..." -> client_secret = "[REDACTED:secret-assignment]"
loci index は、削除した内容をスコープごと、種類ごとに報告します。マスキングが黙って行われると、何も削除されていない場合と区別できません。意図的に過剰なマスキングに偏らせています。誤検出ではチャンク 1 つの検索品質が少し下がるだけですが、検出漏れでは有効な認証情報が平文ファイルやベクトルインデックスにコピーされてしまいます。
設計ルールと、その根拠となる測定結果
妥当な実装でも、次のような間違いを犯します。
1. スコープを1つのインデックスに統合しないでください。 冒頭の表を参照してください。グラフを統合した場合、トピックとの一致率は2%と18%です。
2. どちらの層でも、回答を控えることは機能の1つです。 意図的にでたらめな質問を含むプローブで、10問中10問を測定しました。どのRAGツールも自信満々に「荷物を積んでいないツバメの飛行速度は?」に答えますが、このツールはわからないと答えます。
3. マージンや比率のテストでは、信頼度は測れません。 どちらも試しましたが、実データではどちらも逆の結果になります。*「このバグはどう直せばよいですか?」*ではルーティングマージンが0.94になりますが、正しくルーティングされた質問では0.18になります。でたらめな質問は、大きなスコープに対して0.608のスコアを記録し、小さなスコープに対する本物の質問よりも高いトップ対p90比を示しました。両者の違いを分けるのは、質問の語句がコーパスにそもそも存在するかどうかです。
4. トークンの一致数は、根拠にはなりません。 自動ラベル付けされた質問と比較すると、一致数ではルーティング可能かどうかをまったく見分けられず、むしろ逆の結果になります。トークンの証拠を合計すれば見分けられます。一致数は、OR条件で組み合わせた3つのゲートのうち1つとしてのみ残しています。
5. 直示表現は文法的なものなので、文法的に検出してください。 主題を指し示す質問(「このプロジェクト」「そのアプリ」「それ」など)は、語彙によるルーティングができません。主題を特定する語句を、質問がまさに述べていないからです。語彙上の統計では、この問題を解決できません。start(2.51)とservices(2.56)のトークンごとの証拠は、session(2.53)のような実際の証拠と区別がつきません。マーカーを閉じたクラスとして定義したところ、回答を控える割合は37.5%から100%になりました。
6. スコープサイズで2回正規化してください。 あるスコープが別のスコープより67倍大きい場合、一般的な英単語は一方のスコープにしか現れず、そのスコープを最大限に識別する語としてスコア付けされます。
7. Docstringは検索には最適ですが、ルーティングには不向きです。 「returns」「the value」「default」「configuration」のような、量が多く汎用的な英語が含まれます。これらをルーティングに組み込むと、シンボルインデックス付きコーパスでも散文のみのコーパスでも、精度が半減しました。
8. 制約となるのはランキングではなく、カバレッジです。 これはあらゆる実験で明らかになりました。インデックス化されていない3つのリポジトリに対して、ASTのみのインデックスを無料で実行したところ、競合するスコープをさらに2つ追加したにもかかわらず、ルーティング精度は69.6%から82.6%に向上しました。そのため、doctorは第一級のコマンドです。理由が示された空の回答のほうが、誤ったプロジェクトからの自信満々な回答よりも優れています。
ランキング
エピソード検索では3つのシグナルを統合し、いずれかが結果を返したものについて再正規化します。
| ランカー | 検出するもの |
|---|---|
| BM25 | 正確な用語、珍しい識別子 |
| char 3–5 grams | 語形と大文字・小文字の違い — samesite と SameSite=None |
| embeddings(bge-small、ローカル、オプション) | 共通する語句がなくても意味を捉える |
語彙に基づく2つの手法は、構造グラフではまったく捉えられない質問を拾います。埋め込みランカーは、どちらの語彙ランカーでも拾えない質問を捉えます。つまり、質問とその回答に共通する語彙がない場合です。
埋め込みを追加すると、回答を控える機能が損なわれます。すべてのチャンクがゼロではないスコアを得るため、絶対的な下限値に達しなくなるからです。これを解決するのが2段階ゲートです。ヒットには、語彙による根拠、または意味的な確信のいずれかが必要です。語彙だけのゲートでは、埋め込みを追加した目的そのものである対象を排除してしまいます。
クロスエンコーダーによる再ランキングは--rerankで利用できますが、デフォルトでは無効です。ラベル自体に議論の余地がある6つのケースで測定したところ、小さな精度低下が2件あり、precision@1は2/6から3/6に向上しました。1クエリあたりの所要時間は約96msでした。
パフォーマンス
loci index 15s full · 3.9s when nothing changed
loci ask 1.0s one-shot with --fast · ~5s with semantic ranking
MCP server 6s at boot, then 0.2-0.3s per tool call
routing sub-millisecond, and flat from 25 to 100 scopes
この仕組みを支えているのは3つの要素です。いずれも、まずボトルネックを計測して特定しました。
走査では枝刈りを行い、フィルタリングはしません。 Path.glob("**/*.py") は node_modules、.venv、Pods まで完全に降りていき、1つのリポジトリの列挙に32.9秒かかります。枝刈りによって、速度だけでなくインデックスも改善します。あるプロジェクトでは、枝刈り前はソースの66%がベンダー提供のサードパーティ製コードでした。
字句ランカーはインデックス作成時に適合されます。 広い範囲を対象に文字3~5グラムのTF-IDFを計算するには約1.5秒かかり、プロセス内キャッシュはCLIやMCPの呼び出しをまたいで保持されません。
埋め込みモデルはMCPの起動時に読み込まれます。 インポートとモデルの構築にかかる約2.3秒は、処理時間ではなくプロセスの起動時間です。--fast を指定すると、単発のCLIでの質問ではモデルの読み込みを省略します。
規模
| 範囲の数 | 語彙の重複度 | ルーティング | top-1 |
|---|---|---|---|
| 25~100 | 低 | 0.1~0.5ms | 100% |
| 25~100 | 中程度 | 0.3~0.6ms | 90% |
| 25 | 高 | 0.1ms | 回答は返しますが、通常は誤りです(36%) |
| 50~100 | 高 | 0.5ms | 90%の割合で棄権します |
制限となるのは範囲の数ではなく、語彙の重複度です。範囲の数が増えるほど、失敗はより安全になります。同じ用語を共有する範囲が増えると、どれか1つの範囲を選ぶ根拠が弱まり、ゲートが作動します。危険な構成なのは、互いによく似たプロジェクトからなる小規模なコーパスです。
耐久性
書き込みには一時ファイルと os.replace を使うため、読み取り側が書き込み途中のインデックスを見ることはありません。Path.write_text はデータを書き込む前にファイルを切り詰めます。8MBのストアで計測したところ、同時アクセス時に数秒で11回の破損した読み取りが発生しましたが、変更後は0回でした。MCP サーバーが loci index の実行中に読み取る場合、まさにこのタイミングに重なります。
loci index と loci embed はアドバイザリロックを取得するため、2つのビルドが交互に書き込んで、インデックスとストアの内容が食い違うことはありません。代わりに、同時実行を拒否します。停止したプロセスが残したロックは検出され、解除されます。
読み取り側が参照するのはインデックスであるため、ストアはインデックスより先に書き込まれます。このため、2つの書き込みの間にクラッシュしても、古いインデックスがストア内のデータを参照する状態になりますが、そのストアは古いインデックスの内容をすべて含んでいます。存在しないチャンクがインデックスに記載されることはありません。
拡張
backends/base.py には2つの契約があります。
class StructureBackend(Protocol):
def sources(self, scope) -> list[dict]: ...
def vocabulary(self, scope) -> Counter: ... # feeds the router
def query(self, scope, query, *, budget, dfs) -> list[StructureHit]: ...
class EpisodeBackend(Protocol):
def collect(self, scope) -> list[Chunk]: ... # verbatim, redacted
def search(self, question, chunks, scope_id, *, k, rerank) -> list[EpisodeHit]: ...
backends/ より上のレイヤーは、どの実装が使われているかを把握しません。graphifyをtree-sitter、SCIP、またはLSPインデックスに切り替えるには、ルーターを編集するのではなく、アダプターを1つ作成します。ストアが常に何かを見つける場合、その結果を信頼できないため、search は空のリストを返せる必要があります。
graphifyのフォークではない理由
lociは、graph.json のデータ契約を読み取り、graphify query --graph をシェルから実行する1つのアダプターを通じてgraphifyに依存しています。フォークするとなると、変更する必要のないコードを変更するために、767KBの抽出器と約18個のtree-sitter文法を引き継ぐことになります。lociの開発のきっかけとなったスコープ指定のギャップ、つまりgraphifyは統合グラフのノードに repo 属性を付与するものの、それを使ってフィルタリングする手段がないという問題は、スコープごとにグラフを1つ保持することで、graphifyの上位レイヤーで解決しています。どちらのプロジェクトもMITライセンスなので、フォークすることは引き続き可能ですが、必要はありません。
ステータス
アルファ版です。数値の解釈には注意が必要です。
14 個のスコープにわたる 173 項目のセットで測定しています。そのうち 112 項目は、すべてのスコープに適用される固定の分類体系から生成されているため、この部分には構造上バイアスがありません。残る 61 項目は、ルーターを構築した本人が手作業で作成したもので、それぞれに作成者が何を見ていたかを記録する contamination フィールドがあります。汚染のない結果は、常に別途報告されます。
評価コーパスはこのリポジトリに含まれていません。 ハーネスは実在する 14 個のプロジェクトを読み込み、そのうち 6 個は非公開、または一度もどこにもプッシュされていません。また、記録にはそれらのプロジェクトが file:line 形式で記載されています。そのため、evals/ と、これらのソース全体で参照されている evals/RESULTS.md はローカルに保持され、ここにも sdist にも公開されていません。代わりに同梱されるのは loci eval です。これは、あなたが登録したコーパスから独自に質問を生成します。あなた自身の環境について信頼すべき数値はこれであり、この README が実行を推奨しているのもこれです。
既知の制限:
- コーパスが 10 スコープから 14 スコープに増えると、動作ファミリーのスコアは 85.7% から 28.6% に低下しました。 原因はしきい値ではありません。新しいコーパスで
SIZE_PRIORを 0.0~0.5 の範囲で調整しても、同梱されている 0.15 を上回ることはありません。現在、2 つのスコープには、それぞれ 5,995 個と 4,680 個の異なるトークンが含まれています。一方、urthreadsは 427 個です。語彙がコーパス自体に匹敵するスコープは、一般的な英語の質問すべてに一致します。これに対して 5 種類の代替スコアリングファミリーを検証しましたが、単純な合計を上回るものはありませんでした。唯一の厳密な改善案は、単一のコーパスにおける単一項目では証拠にならないため、CORROBORATION_WEIGHTとして無効のまま同梱されています。これはルーターに残された最大の未解決課題です。 - 合成テスト環境ではこの失敗を再現できません。
CorpusSpec.size_skewが変化させるのはファイルの量であり、語彙の広さではないため、これを解決できる定数をどの値にしても、13 種類すべての形状で 100% になります。これを解消するにはテスト環境の変更が必要であり、その変更がルーターの変更を阻んでいます。 - 今回のリリース中に、評価用の正解データが 2 件古くなっていることが判明しました。そのうち 1 件は、正しい 3 スコープの回答を適合率の失敗として減点していました。コーパスの変化によって正解が記録上の誤りになりますが、ハーネスにはそれを検知する仕組みがありません。
- 構造ストアが回答に該当するシンボルを提示できるのは、7 件のプローブのうち 4 件です。
- 大規模な文章のみのスコープは過剰に選ばれます。数千のチャンクがある一方でコードグラフが存在しないスコープが、本来は別の場所に属する質問を吸収します。
- CI では現在、Python 3.10~3.13 を対象に、Linux、Windows、macOS でテストスイートを実行しています。これを整備する過程で、コードを読んだだけでは見つからなかった 3 つ目のプラットフォーム固有のバグが判明しました。Windows では、別のハンドルが宛先を開いていると
os.replaceが拒否されるため、他の 2 つのプラットフォームでは問題がない一方で、永続化の書き込みがすべて失敗していました。コードを読むことでバグは見つかりますが、このバグは見つかりませんでした。Windows で loci を実行したユーザーは、まだいません。 loci evalとloci calibrateは質問ファミリーを共有するため、調整直後のevalで満点を取っても、ある程度は循環的な結果です。
最も役立つのは、私のものではないコーパスで loci eval を実行し、その結果を教えていただくことです。
ライセンス
MIT。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。