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 是由用户明确触发的蒸馏操作。

心智模型

蒸馏将四种操作整合为一个流程:

  • emerge — 将新记忆聚类到新页面中
  • absorb — 将孤立记忆分配给现有页面;如果有 2 个或更多现有页面链接到某个主题,但没有页面以该主题命名,则根据该主题提议新页面
  • refresh — 根据源记忆重新生成过时页面(仅当用户未编辑该页面时;你编辑过的页面会保持锁定)
  • merge — 合并守护进程全局审查标记的重复页面

默认流程会运行全部四种操作。rebuild 动词是一种具有破坏性的选择加入操作,会覆盖单个页面的锁定状态。

单一流程

向守护进程发送一个 POST 请求。响应分为:

  • pages_created / created_ids:守护进程自行合成的页面 (仅当守护进程配备了 LLM 时)。
  • pending:守护进程未能完成的聚类。Agent 会在本次会话中逐一合成,并将其 POST 回 /api/pages。

后台蒸馏周期与此技能之间唯一的区别在于触发时机。代码路径相同;守护进程无法合成时会交回聚类;调用方负责完成其余工作。

流程

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 会包含守护进程发现的所有聚类。Agent 会在本次会话中合成它们——因此 LLM 的选择与用户调用此技能的方式一致。

unresolved + hint:将其原样转达给用户并停止。

3. 综合每个 pending 集群

守护进程路由会筛掉被现有页面完全覆盖的集群(子集或 Jaccard ≥ 0.8)。剩下的集群要么是:

  • 全新集群(没有现有页面)→ 创建新页面。
  • 刷新候选项(已设置 existing_page_id)→ 集群包含匹配页面中没有的新记忆。Agent 可以访问大模型,因此正确做法是在同一轮处理中刷新现有页面。

集群结构:

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 错误、架构选择、入门界面、迁移和 CSS),则该集群缺乏连贯性。跳过综合。将其记录在报告的“已跳过(连贯性较低)”下,并注明现有页面标题(如果是刷新候选项),或简短的主题提示(如果是新集群)。
  • 连贯的集群(记忆共享实际主题,而不仅仅是实体标签)→ 继续综合。

连贯性判断只有 Agent 能完成,因为它需要阅读文字内容。守护进程的集群划分基于启发式规则;Agent 是避免生成大杂烩页面的最后一道筛选。

对于每个连贯的集群:

  • 标题:简短的名词短语。刷新时使用 existing_page_title,除非新记忆实质性地改变了主题。新集群使用 cluster.entity_name(如果具体明确),否则根据第一条记忆的内容提炼标题。
  • 摘要:一句话——陈述经得起时间考验的主张。
  • 正文:3-7 段 Wiki 风格的文字。使用 [[wikilinks]]。在文中用 (source: mem_XXX) 引用来源 ID。

新集群(没有 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>") 获取来源记忆,执行与集群相同的连贯性检查,然后调用 update_page,传入现有的 source_memory_ids 和重新综合的文字内容。
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 标签。每个条目都是一个主题发现信号——其他页面正在请求这个页面。

不要根据此列表自动创建页面——Agent 手头没有来源记忆,空白占位页面还不如没有页面。将这些主题列入报告,让用户选择是否有意运行 /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. 简洁报告

有三种输出格式。根据实际情况选择。

如果 pending 为空(所有集群都已完全覆盖):

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

如果至少有一个集群已合成:

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 都会返回一个 WriteResult,其 warnings 数组包含守护进程预先格式化的差异行(例如 "v3 → v4: +mem_xyz, +250 chars")。将其原样显示在标题后。如果 warnings 为空,或调用未返回 WriteResult,则回退为:

  • 新页面:v1, synthesized from <K> sources(K = source_ids 长度)
  • 刷新页面:refreshed(单独显示,与之前相同)

这样用户无需打开每个文件,就能准确了解每个页面的变更。

如果一致性检查跳过了至少一个集群:

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)
  ...

如果至少有一个过时页面已刷新:

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

新建或刷新集群采用相同的差异行规则:原样显示 update_page 的 WriteResult 中的 warnings[0];如果不存在,则回退为 refreshed。

如果至少有一个过时页面因 user_edited 而被跳过:

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

此处的措辞应与一致性检查跳过的区块不同,让用户一眼就能分辨两种原因。

同一轮处理中发生多种结果时,将相应区块连续输出。

如果唯一的结果是跳过集群(且 pending 非空),仍然输出“已跳过”区块。这种情况下不要报告“已是最新”——当前范围并非最新,只是候选内容的质量过低。

规则:

  • 使用标题,不要使用页面 ID。 ID 在视觉上容易被截断;标题更清晰。
  • 每个合成的页面占一行。不要在聊天中输出正文——使用 /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/ 写入(从捕获内容自动提交)。等待一秒足以让守护进程释放锁。

何时使用

  • 用户说“提炼”“合成”或“重建 X 上的页面”。
  • 批量导入后——守护进程会在后台处理提炼周期;用户也可以强制执行一轮,以立即查看结果。
  • 当你想清除之前编辑过的页面,并让守护进程根据当前来源重新生成时,使用 /distill rebuild <page-id>。此操作具有破坏性:你撰写的内容会消失。请在完成页面整理后使用,以便页面重新进入自动刷新流程。

何时不使用

  • 简单或一次性的交互。后台调度器会定期刷新。
  • 单次记忆写入 → 守护进程摄取后的增强流程已涵盖此情况。

成本

Agent 合成的每个集群都会计入本次会话的 Token 消耗。如果有大模型,守护进程侧的集群会消耗守护进程的大模型 Token(API 调用花费几分钱,设备端运行花费几秒钟)。无论哪种情况,都应合理控制集群大小——守护进程已通过调优配置为每个集群设定 Token 预算。

评论

支持 Markdown