mcpField
D
distill

distill

github.com/davepoon生産性3.7k59 回インストール1 日前に更新
リポジトリ

distill について

distill は、保存した記憶を整理してページにまとめる Origin のエージェントスキルです。一貫性のある記憶群からページを作成し、元の記憶が変わったページを更新するほか、競合や要確認のトピックを報告します。

主な機能

  1. 01記憶をクラスタ化してページを作成
  2. 02未整理の記憶を既存ページに割り当て
  3. 03未編集で古くなったページを更新
  4. 04重複ページを統合
  5. 05競合とトピック候補を報告

活用シーン

  1. 01一括インポート後の記憶整理
  2. 02元の記憶の変更後にページを更新
  3. 03編集を破棄すると決めたページの再構築

MCP 対応

Origin MCP ツールを使って記憶を整理し、ページを作成または更新します。古くなったページの確認時には、そのページのソースも取得します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/davepoon/buildwithclaude --skill distill

インストール後、Claude Code で/distillを入力して読み込まれたか確認します。

詳細

/distill

今すぐ蒸留処理を実行します。デーモンのバックグラウンド蒸留サイクルは独自のタイミングで実行されますが、/distill はユーザーが明示的に起動する処理です。

メンタルモデル

蒸留は、次の4つの操作を1つのフローにまとめたものです。

  • emerge — 新しい記憶をクラスタリングして、新しいページを作成します
  • absorb — 孤立した記憶を既存のページに割り当て、2つ以上の既存ページがリンクしているものの、該当するページがないトピックから新しいページを提案します
  • refresh — 古くなったページを元の記憶から再生成します(ユーザーがページを編集していない場合のみ。変更を加えたページはロックされたままです)
  • merge — デーモンのグローバルレビューで重複としてフラグ付けされたページを統合します

デフォルトのフローでは、4つすべてを実行します。rebuild 動詞は、1つのページのロックを解除する破壊的なオプトイン操作です。

単一フロー

デーモンへの POST は1回です。レスポンスは次のように分かれます。

  • pages_created / created_ids:デーモン自身が合成したページです(デーモンに LLM がある場合のみ)。
  • pending:デーモンが処理を完了できなかったクラスタです。エージェントがこのセッションでそれぞれを合成し、/api/pages に POST して返します。

バックグラウンド蒸留サイクルとこのスキルで異なるのは、起動のタイミングだけです。コードパスは同じです。デーモンは合成できない場合にクラスタを返し、呼び出し元が残りを処理します。

フロー

1. スコープを選ぶ

引数なしの /distill の場合、現在の作業ディレクトリから対象を推定します。

Bash: top=$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null); \
      common=$(git -C "$PWD" rev-parse --git-common-dir 2>/dev/null); \
      if [ -n "$common" ]; then \
        case "$common" in /*) root=$(dirname "$common");; *) root=$(cd "$top" && cd "$(dirname "$common")" && pwd);; esac; \
        basename "$root"; \
      fi
  • 出力がある場合 → それを使用します(例:origin)。
  • Git リポジトリではない場合 → basename "$PWD" にフォールバックします。
  • 予約キーワード deep → スコープなし(グローバル処理)。
  • 予約キーワードの並び rebuild <page-id> → distill(target=<page-id>, force=true) を呼び出します。続行する前に「ページ を再構築しますか?編集内容は破棄され、ページはソースから再生成されます。」と確認します。このスキルの残りの手順はスキップしてください。単一ページの再構築では pending クラスタは生成されません。デーモンのレスポンス形式は {"status": "ok", "force": true, "page_id": ..., "updated": true} です。そのまま報告してください。

/distill <arg> の場合は、<arg> を target に渡します。

2. MCP ツールを呼び出す
distill(target="<scope>")

このツールは、デーモンの完全な JSON ペイロードをテキストとして返します。JSON として解析してください。想定される形式は次のとおりです。

{
  "pages_created": 0,
  "scoped": true,
  "created_ids": [],
  "pending": [
    { "source_ids": [...], "contents": [...], "entity_id": ...,
      "entity_name": ..., "space": ..., "estimated_tokens": ... },
    ...
  ],
  "stale_pages": [
    { "page_id": ..., "title": ..., "summary": ...,
      "source_memory_ids": [...], "stale_reason": "source_updated",
      "user_edited": false, "sources_updated_count": 3 },
    ...
  ],
  "stale_truncated": false,
  "orphan_topics": [
    { "label": "Topic Z", "count": 3 },
    ...
  ]
}

このルートがデーモンの LLM を呼び出すことはありません。このスキルから呼び出した場合、created_ids は常に空です。pending にはデーモンが検出したすべてのクラスタが含まれます。エージェントがこのセッションでそれらを合成します。そのため、LLM の選択はユーザーがこのスキルを呼び出した方法と一致します。

unresolved + hint は、そのままユーザーに伝えて終了します。

3. pending クラスターをそれぞれ合成する

デーモンのルートでは、既存のページで完全にカバーされているクラスター(サブセット、または Jaccard ≥ 0.8)を除外します。残るのは次のいずれかです。

  • 新規クラスター(既存ページなし)→ 新しいページを作成します。
  • 更新候補(existing_page_id が設定されている)→ クラスターには、一致したページに含まれていない新しいメモリーがあります。エージェントは LLM にアクセスできるため、同じ処理中に既存ページを更新するのが適切です。

クラスターの形式:

pending: [
  {
    source_ids, contents, entity_id, entity_name, space,
    estimated_tokens,
    existing_page_id?, existing_page_title?, new_memory_count?
  },
  ...
]

各クラスターについて、合成する前にまず一貫性を確認します。

  • cluster.contents 内のすべてのメモリーに目を通します。
  • クラスターに約 4 件以上のメモリーがあり、トピックが散在している場合(エンティティは共通していても、メモリーが無関係なサブトピックを扱っている場合。たとえば、すべて Origin のタグが付いているものの、RwLock のバグ、スキーマの選択、オンボーディング UI、マイグレーション、CSS にまたがる場合)、そのクラスターには一貫性がありません。合成をスキップします。既存ページのタイトル(更新の場合)、または簡潔なトピックのヒント(新規の場合)を「一貫性が低いためスキップ」に記録します。
  • 一貫性のあるクラスター(エンティティのタグだけでなく、メモリーが実際のトピックを共有している場合)→ 合成に進みます。

一貫性の判断は、文章を読む必要があるため、エージェントにしかできません。デーモンによるクラスター化はヒューリスティックです。寄せ集めのページを生成しないための最終フィルターはエージェントです。

一貫性のあるクラスターごとに、次のようにします。

  • タイトル:短い名詞句にします。更新時は、新しいメモリーによってトピックが大きく変わらない限り、existing_page_title を使用します。新規クラスターの場合は、具体的であれば cluster.entity_name を使用し、そうでなければ最初のメモリーの内容から導きます。
  • 要約:永続的な主張を 1 文で記述します。
  • 本文:Wiki 形式の文章を 3~7 段落で記述します。[[wikilinks]] を使用します。ソース ID は (source: mem_XXX) の形式で本文中に記載します。

新規クラスター(existing_page_id なし)— MCP ツールを呼び出します。

create_page(title="...", summary="...", content="...",
            entity_id="<cluster.entity_id or omit>",
            space="<cluster.space>",
            source_memory_ids=[...])

更新候補(existing_page_id が設定されている)— update_page MCP ツールを使用して、本文をその場で置き換えます。これは単一のアトミックな呼び出しです。コンテンツとソースリストを置き換え、任意で要約を更新し、デーモンの stale_reason をクリアしてバージョンを更新します。また、page_id と created_at は維持されるため、外部の [[wikilinks]] は引き続き機能します。

update_page(page_id=cluster.existing_page_id,
            content="...",
            source_memory_ids=cluster.source_ids,
            summary="...")
3.5 古くなったページを更新する

レスポンスの stale_pages ブロックには、前回のコンパイル以降にソースが変更されたページが一覧表示されます。形式:

stale_pages: [
  { page_id, title, summary, source_memory_ids,
    sources_updated_count, stale_reason, user_edited },
  ...
]
stale_truncated: <bool>   # true when 10+ stale pages exist

古くなったページごとに、次のようにします。

  • user_edited == true → 自動で書き換えてはいけません。ユーザーがページを編集しており、上流のメモリーも変更されています。「競合」レポートブロックに表示して処理を停止します。ユーザーは手動で解決するか、/distill rebuild <page-id> を実行して編集内容を消去し、ソースから再生成します。
  • user_edited == false → get_page_sources(page_id="<id>") でソースメモリーを取得し、クラスターに使用したものと同じ一貫性チェックを実行してから、既存の source_memory_ids と新たに合成した文章を指定して update_page を呼び出します。
update_page(page_id=stale.page_id,
            content="<refreshed prose>",
            source_memory_ids=stale.source_memory_ids,
            summary="<optional refreshed claim>")

stale_truncated == true の場合は、「古くなったページがまだ残っています。この処理の後に /distill を再実行して続行してください」とユーザーに伝えます。

3.6 孤立したトピックの候補を提示する

orphan_topics には、既存のページの 2 件以上がリンク先として参照しているものの、その名前のページが存在しない wikilink ラベルが一覧表示されます。各項目はトピック発見のシグナルです。ほかのページがそのページを求めています。

この一覧からページを自動作成しないでください。エージェントはソースメモリーを手元に持っておらず、内容のない空のページを作るより、ページを作らないほうがよいためです。ユーザーが /distill <label> を意図的に実行できるよう、レポートに表示します。

Topic suggestions (other pages link here, no page yet):
  - "Topic Z"  (3 pages reference it)
  - "Other"    (2 pages reference it)

orphan_topics が空の場合は、このセクションを省略します。

4. 簡潔に報告する

出力形式は3種類です。発生した内容に合うものを選びます。

pending が空の場合(すべてのクラスターがすでに完全に網羅されている場合):

Scope `<scope>` is up to date — no new memories to distill.

少なくとも1つのクラスターを統合した場合:

Distilled N page(s) from <total> memories in scope `<scope>`:
  - <Title>  v1, synthesized from <K> sources
  - <Title>  v3 → v4: +mem_xyz, +250 chars
  ...

各ページについて、create_page と update_page は、デーモンが事前にフォーマットした差分行(例:"v3 → v4: +mem_xyz, +250 chars")を含む warnings 配列を持つ WriteResult を返します。タイトルの後にそのまま表示してください。warnings が空の場合、または呼び出しから WriteResult が返されなかった場合は、次の形式にします。

  • 新規ページ:v1, synthesized from <K> sources(K = source_ids の長さ)
  • 更新されたページ:refreshed(以前と同様、これだけを表示)

これにより、各ファイルを開かなくても、ページごとの変更内容を正確に確認できます。

整合性チェックで少なくとも1つのクラスターをスキップした場合:

Skipped M cluster(s) — low coherence (memories share entity but
topics scatter; would produce a grab-bag page):
  - "<existing_page_title or topic hint>"  (<N> memories)
  ...

少なくとも1つの古いページを更新した場合:

Refreshed K stale page(s):
  - <Title>  v2 → v3: +mem_abc, +180 chars
  - <Title>  refreshed
  ...

新規作成/更新されたクラスターと同じ差分行のルールに従います。update_page の WriteResult から warnings[0] をそのまま表示し、ない場合は refreshed を表示します。

少なくとも1つの古いページを user_edited を理由にスキップした場合:

Conflict on L stale page(s) — page has user edits, sources also
changed. Open and reconcile manually:
  - <Title>  (~/.origin/pages/<slug>.md)

ユーザーが2つの理由を一目で見分けられるように、整合性チェックでスキップした場合とは異なる表現にします。

同じ実行で複数の結果が発生した場合は、各ブロックを続けて出力します。

唯一の結果がクラスターのスキップで(pending が空でなかった場合)も、Skipped ブロックを出力します。この場合、「最新です」と報告してはいけません。対象範囲は最新ではなく、候補の品質が低すぎただけです。

ルール:

  • ページ ID ではなくタイトルを使います。 ID は表示上省略されるため、タイトルのほうが読みやすくなります。
  • 統合したページごとに1行で表示します。チャットには本文を出力しません。本文は /read "<title>" で確認します。
  • <total> = 実際に統合したクラスターの source_ids.len() の合計です。
  • 想定より生成されたページが少ない場合は、クラスター化のしきい値が原因です。多くの記憶は、3つ以上のクラスターを形成するための十分な関連項目がなく、単独で存在します。同じトピックの情報をさらに記録して増やしてください。

~/.origin/ を自動コミットする

上記のページを書き込んだ後、変更をスナップショットして、ユーザーが git log で記憶の変遷を確認できるようにします。安全策として、git が見つからない場合や ~/.origin/ がまだリポジトリでない場合は、何も表示せずにスキップします。

Bash: git -C ~/.origin add -A && \
      git -C ~/.origin -c user.name=Origin -c [email protected] \
          commit --quiet -m "distill: <N> pages" 2>/dev/null || \
      (sleep 1 && git -C ~/.origin add -A && \
       git -C ~/.origin -c user.name=Origin -c [email protected] \
           commit --quiet -m "distill: <N> pages" 2>/dev/null) || true

再試行により index.lock との競合に対処します。キャプチャ時の自動コミットで、デーモンが同時に ~/.origin/ に書き込んでいる可能性があります。デーモンがロックを解放するには、1秒待てば十分です。

使用する場合

  • ユーザーが「distill」「synthesize」「X のページを再構築」と言った場合。
  • 一括インポートの後。デーモンの蒸留サイクルはバックグラウンドで処理しますが、ユーザーはすぐに結果を確認するため、実行を強制できます。
  • 以前編集したページを消去し、デーモンに現在のソースから再生成させたい場合は、/distill rebuild <page-id> を使います。破壊的な操作で、ユーザーの文章は失われます。ページの整理が完了し、自動更新の対象に戻したい場合に使用します。

使用しない場合

  • 些細なやり取りや一度きりのやり取り。定期的な更新はバックグラウンドスケジューラーが対応します。
  • 記憶を1件だけ書き込む場合。デーモンの取り込み後のエンリッチメントがすでに対応しています。

コスト

エージェントが統合する各クラスターは、このセッションのトークンを消費します。デーモン側のクラスター(LLM がある場合)は、代わりにデーモンの LLM トークンを消費します(API では数セント、デバイス上では数秒)。いずれの場合も、クラスターのサイズは適切に保ってください。デーモンはすでにチューニング設定を通じて、クラスターごとのトークン予算を適用しています。

コメント